Update outline renderer

This commit is contained in:
Tom 2024-10-12 15:10:10 +01:00
parent d70a8c5b3e
commit 740ca32920
11 changed files with 297 additions and 84 deletions

View File

@ -25,7 +25,7 @@ head: |
</script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "/assets/projects/bike_lights/models/bigger.glb" zoom=37>
<outline-model-viewer model = "/assets/projects/bike_lights/models/bigger.glb" camera='{"position":[-7.434,5.128,-6.379],"rotation":[-2.464,-0.7373,-2.646],"zoom":303.06369033128976,"target":[0,0,0]}'>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>

View File

@ -26,7 +26,7 @@ head: |
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "{{page.models}}/model.glb" zoom=60>
<outline-model-viewer model = "{{page.models}}/model.glb" camera='{"position":[6.039,6.456,-6.641],"rotation":[-2.37,0.5778,2.654],"zoom":309.7389923355519,"target":[0,0,0]}'>
<img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>

View File

@ -25,7 +25,7 @@ head: |
</script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "{{page.model}}" camera='{"position":[2.11,4.722,9.765],"rotation":[-0.4425,0.1813,0.08522],"zoom":471.1588632880538,"target":[0.1159,0.06564,-0.06329]}'>
<outline-model-viewer model = "{{page.model}}" camera='{"position":[7.699,4.641,6.436],"rotation":[-0.6243,0.7663,0.4633],"zoom":229.77238881409951,"target":[0,0,0]}'>
<img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>

View File

@ -25,7 +25,7 @@ head: |
</script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "{{page.model}}" camera='{"position":[5.148,4.038,8.952],"rotation":[-0.4169,0.4809,0.2021],"zoom":1248.587161014231,"target":[0.03319,0.06938,-0.01135]}'>
<outline-model-viewer model = "{{page.model}}" camera='{"position":[7.699,4.641,6.436],"rotation":[-0.6243,0.7663,0.4633],"zoom":229,"target":[0.0,0,0]}'>
<img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>

View File

@ -85,35 +85,39 @@ I've put a 240x240 pixel colour screen on the front to show metrics like total c
## Electronics
Because I am taking this way to far, I wanted to do per port enable/disable and current monitoring. To implement this I'm designing a PCB with 5 channels where each channel consists of this schematic.
Because I am taking this way too far, I wanted to do per port enable/disable and current monitoring. To implement this I'm designing a PCB with 5 channels where each channel consists of this schematic.
<figure style="max-width: 350px;">
<img src="{{page.assets}}/channel_sch.png">
</figure>
There's an INA219 and a shunt resistor for current and voltage monitoring and a chunky MOSFET for enabling and disabling the channel.
TODO:
Check the power dissipated in the MOSFET when the gate is driven at 3.3V
Check the inrush current when the MOSFET switches on and off, could potentially limit this by using a larger gate resistor to turn the MOSFET on more slowly.
There's an INA219 and a shunt resistor for current and voltage monitoring and a chunky MOSFET for enabling and disabling the channel.
<figure class="two-wide">
<img src="{{page.assets}}/channel_board.png">
<img src="{{page.assets}}/channel_3d.png">
</figure>
For now I've broken the functionality for one channel out into a test board that I've sent off to JLCPB for manufacturing with and to be populated with SMT components. This ended up costing about 50 dollars for 5 boards. In future I want to have a go at doing the component placement and reflow myself.
<outline-model-viewer model = "{{page.assets}}/test_board.glb" true-color=true spin=true camera='{"position":[4.016,7.557,6.841],"rotation":[-0.8351,0.3753,0.3848],"zoom":241.86567243589988,"target":[0,0,0]}'>
<img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>
## Software
In other posts I've described how I made this simulator the test out possible GUIs for this thing.
TODO: Add some knobs to the simulator so you can test different conditions such as overcurrent, overtemp, sleep, nightmode etc.
<usbc-power-supply-simulator disable-console disable-editor code="/assets/blog/micropython/demo.py"></usbc-power-supply-simulator>
<!-- <usbc-power-supply-simulator disable-console disable-editor code="/assets/blog/micropython/demo.py"></usbc-power-supply-simulator>
-->
<outline-model-viewer model = "{{page.model}}" true-color=true spin=false camera='{"position":[-6.425,8.003,-3.751],"rotation":[-2.016,-0.6378,-2.246],"zoom":6784.844370099355,"target":[0.1581,-0.01497,0.07167]}'>
<!-- <outline-model-viewer model = "{{page.model}}" true-color=true spin=false camera='{"position":[-6.425,8.003,-3.751],"rotation":[-2.016,-0.6378,-2.246],"zoom":6784.844370099355,"target":[0.1581,-0.01497,0.07167]}'>
<img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>
</outline-model-viewer> -->
<!-- <kicanvas-embed src="/assets/projects/usbc_power_supply/usb-c_psu.kicad_sch" controls="basic"> </kicanvas-embed> -->

View File

@ -24,7 +24,7 @@ head: |
</script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet.glb" zoom=500 camera='{"position":[-4.187,2.613,-9.927],"rotation":[-2.895,-0.3904,-3.046],"zoom":715.9863905262143,"target":[0.02078,0.1128,-0.01309]}'>
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet.glb" zoom=500 camera='{"position":[3.118,3.203,10.1],"rotation":[-0.3104,0.2858,0.0902],"zoom":428.68750000000136,"target":[0,0,0]}'>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>
@ -35,7 +35,7 @@ Check out a little interactive model of the magnetometer below. The device has t
Here's a cutaway view of the interior.
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet_section.glb" zoom=500 spin=false camera='{"position":[-3.078,3.353,10.11],"rotation":[-0.309,-0.2822,-0.08866],"zoom":9794.920097823839,"target":[0.0006876,0.1232,-0.005368]}' true-color=true>
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet_section.glb" spin=false camera='{"position":[-3.069,3.172,10.17],"rotation":[-0.3052,-0.2811,-0.08718],"zoom":2860.0091628398345,"target":[0.007077,-0.02863,0.01116]}' true-color=true>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>

View File

@ -95,6 +95,24 @@ class USBCPowerSupplySimulator extends HTMLElement {
run_button.onclick = runPython;
if (editor_disabled) run_button.style.display = "none";
runPython();
// Only start simulation when the element is visible
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log(
"Micropython simulator is visible. Starting execution..."
);
runPython();
} else {
console.log(
"Micropython simulator is not visible. Would pause if I could..."
);
}
});
});
observer.observe(this);
}
constructor() {

View File

@ -12,7 +12,7 @@ class FindSurfaces {
constructor() {
// This identifier, must be globally unique for each surface
// across all geometry rendered on screen
this.surfaceId = 0;
this.surfaceId = 10;
}
/*
@ -21,7 +21,18 @@ class FindSurfaces {
getSurfaceIdAttribute(mesh) {
const bufferGeometry = mesh.geometry;
const numVertices = bufferGeometry.attributes.position.count;
const vertexIdToSurfaceId = this._generateSurfaceIds(mesh);
// Check if "track" or "pad" is in the name of the mesh
let idOverride = null;
if (
mesh.name.includes("track") ||
mesh.name.includes("pad") ||
mesh.name.includes("zone")
) {
idOverride = 1;
}
const vertexIdToSurfaceId = this._generateSurfaceIds(mesh, idOverride);
const colors = [];
for (let i = 0; i < numVertices; i++) {
@ -39,7 +50,7 @@ class FindSurfaces {
* Returns a `vertexIdToSurfaceId` map
* given a vertex, returns the surfaceId
*/
_generateSurfaceIds(mesh) {
_generateSurfaceIds(mesh, idOverride = null) {
const bufferGeometry = mesh.geometry;
const numVertices = bufferGeometry.attributes.position.count;
const numIndices = bufferGeometry.index.count;
@ -78,7 +89,7 @@ class FindSurfaces {
// Mark them as explored
for (let v of surfaceVertices) {
exploredNodes[v] = true;
vertexIdToSurfaceId[v] = this.surfaceId;
vertexIdToSurfaceId[v] = idOverride || this.surfaceId;
}
this.surfaceId += 1;
@ -166,4 +177,4 @@ function getFragmentShader() {
gl_FragColor = vec4(surfaceId, 0.0, 0.0, 1.0);
}
`;
}
}

View File

@ -10,42 +10,95 @@ import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
import GUI from 'lil-gui'
import GUI from "lil-gui";
import { CustomOutlinePass } from "./CustomOutlinePass.js";
import FindSurfaces from "./FindSurfaces.js";
// Todo:
// Swap in the version of this code that has a debug GUI behind a flag
// Consider support for transparent objects by rendering them as a wireframe in the color and excluding them from the edge pass.
// Fix the cetnering and scaling
// Switch to an angled isometric camera to match the style from the main page.
// Function to lighten or darken a color based on brightness
function adjustColor(color, factor) {
const hsl = color.getHSL({}); // Get the HSL values of the current color
if (hsl.l > 0.7) {
// If the color is light, darken it
hsl.l = Math.max(0, hsl.l - factor);
} else {
// If the color is dark, lighten it
hsl.l = Math.min(1, hsl.l + factor);
}
color.setHSL(hsl.h, hsl.s, hsl.l); // Set the adjusted color
}
function printGLTFScene(scene, maxDepth = 3, depth = 0, indent = 0) {
// Helper function to format the output
if (depth > maxDepth) {
return;
}
const pad = (level) => " ".repeat(level * 2);
// Recursively print scene contents
scene.traverse((object) => {
console.log(
`${pad(indent)}- ${object.type} ${
object.name || "(unnamed)"
} | Position: (${object.position.x.toFixed(
2
)}, ${object.position.y.toFixed(2)}, ${object.position.z.toFixed(2)})`
);
if (object.children && object.children.length > 0) {
console.log(`${pad(indent + 1)}Children:`);
object.children.forEach((child) =>
printGLTFScene(child, maxDepth, depth + 1, indent + 2)
);
}
});
}
const serialiseCamera = (camera, controls) => {
const position = Object.values(camera.position);
const extractXYZ = ({_x, _y, _z}) => [_x, _y, _z];
const extractXYZ = ({ _x, _y, _z }) => [_x, _y, _z];
const rotation = extractXYZ(camera.rotation);
const fixed = (l) => l.map( x => parseFloat(x.toPrecision(4)))
const fixed = (l) => l.map((x) => parseFloat(x.toPrecision(4)));
return JSON.stringify({
position: fixed(position),
rotation: fixed(rotation),
zoom: camera.zoom,
target: fixed(Object.values(controls.target)),
});
position: fixed(position),
rotation: fixed(rotation),
zoom: camera.zoom,
target: fixed(Object.values(controls.target)),
});
};
class OutlineModelViewer extends HTMLElement {
constructor() {
super();
let component_rect = this.getBoundingClientRect();
console.log("component_rect", component_rect);
this.isVisible = true; // Track visibility
this.shadow = this.attachShadow({ mode: "open" });
// Mouse and raycaster
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.intersectedObject = null; // Store currently intersected object
}
// Handle mouse movement and update mouse coordinates
onMouseMove(event, canvas) {
const rect = canvas.getBoundingClientRect();
this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
}
connectedCallback() {
let component_rect = this.getBoundingClientRect();
this.render(component_rect.height);
const model_path = this.getAttribute("model") || "/assets/projects/bike_lights/models/bigger.glb";
const spin = (this.getAttribute("spin") || 'true') === 'true'
const model_path =
this.getAttribute("model") ||
"/assets/projects/bike_lights/models/bigger.glb";
const spin = (this.getAttribute("spin") || "true") === "true";
const container = this.shadow.querySelector("div#container");
const canvas = this.shadow.querySelector("canvas");
@ -53,7 +106,7 @@ class OutlineModelViewer extends HTMLElement {
let canvas_rect = canvas.getBoundingClientRect();
console.log(canvas_rect);
// determine the outline and bg colors
// determine the outline and bg colors
const body = document.getElementsByTagName("body")[0];
const style = window.getComputedStyle(body);
const outline_color = style.getPropertyValue("--theme-model-line-color");
@ -61,8 +114,15 @@ class OutlineModelViewer extends HTMLElement {
// // Init scene
// const camera = new THREE.PerspectiveCamera(70, canvas_rect.width / canvas_rect.height, 0.1, 100);
const camera = new THREE.OrthographicCamera( canvas_rect.width / - 2, canvas_rect.width / 2, canvas_rect.height / 2, canvas_rect.height / - 2, 1, 1000 );
camera.zoom = parseFloat(this.getAttribute("zoom") || "1")
const camera = new THREE.OrthographicCamera(
canvas_rect.width / -2,
canvas_rect.width / 2,
canvas_rect.height / 2,
canvas_rect.height / -2,
1,
1000
);
camera.zoom = parseFloat(this.getAttribute("zoom") || "1");
camera.position.set(10, 2.5, 4);
// create the scene and the camera
@ -75,7 +135,7 @@ class OutlineModelViewer extends HTMLElement {
// renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(canvas_rect.width, canvas_rect.height, false);
const light = new THREE.DirectionalLight(0xffffff, 2);
scene.add(light);
light.position.set(1.7, 1, -1);
@ -87,8 +147,8 @@ class OutlineModelViewer extends HTMLElement {
// See: https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderTarget.depthBuffer
const depthTexture = new THREE.DepthTexture();
const renderTarget = new THREE.WebGLRenderTarget(
2*canvas_rect.width,
2*canvas_rect.height,
2 * canvas_rect.width,
2 * canvas_rect.height,
{
depthTexture: depthTexture,
depthBuffer: true,
@ -102,18 +162,18 @@ class OutlineModelViewer extends HTMLElement {
// Outline pass.
const customOutline = new CustomOutlinePass(
new THREE.Vector2(2*canvas_rect.width, 2*canvas_rect.height),
new THREE.Vector2(2 * canvas_rect.width, 2 * canvas_rect.height),
scene,
camera,
outline_color,
outline_color
);
composer.addPass(customOutline);
// Antialias pass.
const effectFXAA = new ShaderPass(FXAAShader);
effectFXAA.uniforms["resolution"].value.set(
.5 / canvas_rect.width,
.5 / canvas_rect.height
0.5 / canvas_rect.width,
0.5 / canvas_rect.height
);
composer.addPass(effectFXAA);
@ -121,14 +181,31 @@ class OutlineModelViewer extends HTMLElement {
// Load model
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderConfig({ type: 'js' });
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader( dracoLoader );
dracoLoader.setDecoderConfig({ type: "js" });
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
loader.setDRACOLoader(dracoLoader);
loader.load(model_path, (gltf) => {
scene.add(gltf.scene);
surfaceFinder.surfaceId = 0;
// Compute bounding box
let box = new THREE.Box3().setFromObject(gltf.scene);
// Scale the model to fit into a unit cube
const size = new THREE.Vector3();
box.getSize(size); // Get the size of the bounding box
const maxDim = Math.max(size.x, size.y, size.z); // Find the largest dimension
const scaleFactor = 1 / maxDim; // Calculate the scaling factor
gltf.scene.scale.set(scaleFactor, scaleFactor, scaleFactor); // Apply the scale uniformly
// Reposition the model so that its center is at the origin
let box2 = new THREE.Box3().setFromObject(gltf.scene);
const center = new THREE.Vector3();
box2.getCenter(center); // Get the center of the bounding box
gltf.scene.position.sub(center); // Subtract the center from the position
// Modify the materials to support surface coloring
scene.traverse((node) => {
if (node.type == "Mesh") {
const colorsTypedArray = surfaceFinder.getSurfaceIdAttribute(node);
@ -136,14 +213,34 @@ class OutlineModelViewer extends HTMLElement {
"color",
new THREE.BufferAttribute(colorsTypedArray, 4)
);
const material_params = this.getAttribute("true-color") ? {color: node.material.color} : {emissive: model_color};
let material_params = this.getAttribute("true-color")
? { color: node.material.color }
: { emissive: model_color };
if (node.name.includes("track") || node.name.includes("zone")) {
//set to a copper colour
// #c87533
material_params = {
color: new THREE.Color(0x558855),
};
node.position.y += 0.00001;
}
if (node.name.includes("pad")) {
material_params = {
color: new THREE.Color(0xaaaaaa),
};
node.position.y += 0.00002;
}
// override materials
node.material = new THREE.MeshStandardMaterial(material_params);
}
});
customOutline.updateMaxSurfaceId(surfaceFinder.surfaceId + 1);
// Print out the scene structure to the console
// printGLTFScene(gltf.scene, 1);
});
// Set up orbital camera controls.
@ -151,7 +248,7 @@ class OutlineModelViewer extends HTMLElement {
controls.autoRotate = spin;
controls.update();
if(this.getAttribute("camera")) {
if (this.getAttribute("camera")) {
const cameraState = JSON.parse(this.getAttribute("camera"));
camera.zoom = cameraState.zoom;
camera.position.set(...cameraState.position);
@ -159,31 +256,102 @@ class OutlineModelViewer extends HTMLElement {
controls.target.set(...cameraState.target);
}
// Event listener for mouse movement
canvas.addEventListener("mousemove", (event) =>
this.onMouseMove(event, canvas)
);
let intersects = [];
const doRayCast = () => {
// Perform raycasting for hovering
this.raycaster.setFromCamera(this.mouse, camera);
intersects.length = 0;
this.raycaster.intersectObjects(scene.children, true, intersects);
if (intersects.length > 0) {
const object = intersects[0].object;
// If the intersected object has changed
if (this.intersectedObject !== object) {
if (this.intersectedObject) {
// Reset the color of the previously hovered object
this.intersectedObject.material.emissive.setHex(
this.intersectedObject.currentHex
);
}
// Store the current hex color and set highlight color
this.intersectedObject = object;
this.intersectedObject.currentHex =
this.intersectedObject.material.emissive.getHex();
// Adjust the emissive color based on current brightness
const currentColor = new THREE.Color(
this.intersectedObject.material.emissive.getHex()
);
adjustColor(currentColor, 0.2); // Lighten or darken based on brightness
this.intersectedObject.material.emissive.set(currentColor);
// Print the name of the intersected object
params.selectedObject = object.name || "(unnamed object)";
}
} else if (this.intersectedObject) {
// Reset the color if the mouse is no longer hovering over any object
this.intersectedObject.material.emissive.setHex(
this.intersectedObject.currentHex
);
this.intersectedObject = null;
params.selectedObject = "";
}
};
window.addEventListener("click", doRayCast);
// Render loop
function update() {
requestAnimationFrame(update);
controls.update();
composer.render();
}
const update = () => {
if (this.isVisible) {
requestAnimationFrame(update);
controls.update();
composer.render();
// doRayCast();
}
};
update();
// Pausing/resuming the render loop when element visibility changes
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("Model Viewer Element is visible. Resuming rendering...");
this.isVisible = true;
update(); // Resume the loop
} else {
console.log(
"Model Viewer Element is not visible. Pausing rendering..."
);
this.isVisible = false; // Pauses rendering
}
});
});
// Observe this element for visibility changes
observer.observe(this);
function onWindowResize() {
canvas_rect = canvas.getBoundingClientRect();
camera.aspect = canvas_rect.width / canvas_rect.height;
camera.updateProjectionMatrix();
renderer.setSize(canvas_rect.width, canvas_rect.height, false);
composer.setSize(2*canvas_rect.width, 2*canvas_rect.height);
effectFXAA.setSize(2*canvas_rect.width, 2*canvas_rect.height);
customOutline.setSize(2*canvas_rect.width, 2*canvas_rect.height);
composer.setSize(2 * canvas_rect.width, 2 * canvas_rect.height);
effectFXAA.setSize(2 * canvas_rect.width, 2 * canvas_rect.height);
customOutline.setSize(2 * canvas_rect.width, 2 * canvas_rect.height);
effectFXAA.uniforms["resolution"].value.set(
.5 / canvas_rect.width,
.5 / canvas_rect.height
0.5 / canvas_rect.width,
0.5 / canvas_rect.height
);
}
window.addEventListener("resize", onWindowResize, false);
const gui = new GUI({
title: "Settings",
container: container,
@ -191,43 +359,49 @@ class OutlineModelViewer extends HTMLElement {
closeFolders: true,
});
gui.close();
const uniforms = customOutline.fsQuad.material.uniforms;
const params = {
selectedObject: "None",
spin: controls.autoRotate,
mode: { Mode: 0 },
depthBias: uniforms.multiplierParameters.value.x,
depthMult: uniforms.multiplierParameters.value.y,
FXAA_resolution: 0.5,
printCamera: () => console.log(serialiseCamera(camera, controls)),
};
gui.add(params, 'printCamera' );
gui.add(params, "selectedObject").listen();
gui.add(params, "spin").onChange((value) => {
controls.autoRotate = value;
});
gui.add(params, "printCamera");
gui
.add(params.mode, "Mode", {
"Outlines + Shaded (default)": 0,
"Shaded": 2,
Shaded: 2,
"Depth buffer": 3,
"SurfaceID buffer": 4,
"Outlines": 5,
Outlines: 5,
})
.onChange(function (value) {
uniforms.debugVisualize.value = value;
});
gui.add(params, "depthBias", 0.0, 5).onChange(function (value) {
uniforms.multiplierParameters.value.x = value;
});
gui.add(params, "depthMult", 0.0, 20).onChange(function (value) {
uniforms.multiplierParameters.value.y = value;
});
gui.add(params, "FXAA_resolution", 0.0, 2).onChange(value => {
effectFXAA.uniforms["resolution"].value.set(
gui.add(params, "depthBias", 0.0, 5).onChange(function (value) {
uniforms.multiplierParameters.value.x = value;
});
gui.add(params, "depthMult", 0.0, 20).onChange(function (value) {
uniforms.multiplierParameters.value.y = value;
});
gui.add(params, "FXAA_resolution", 0.0, 2).onChange((value) => {
effectFXAA.uniforms["resolution"].value.set(
value / canvas_rect.width,
value / canvas_rect.height
);})
);
});
}
render(height) {
@ -266,6 +440,11 @@ class OutlineModelViewer extends HTMLElement {
border: var(--theme-subtle-outline) 1px solid;
}
.lil-gui .controller.string input {
background-color: var(--theme-subtle-outline);
color: var(--theme-text-color);
}
canvas {
position: absolute;
width: 100%;
@ -277,4 +456,4 @@ class OutlineModelViewer extends HTMLElement {
}
}
customElements.define("outline-model-viewer", OutlineModelViewer);
customElements.define("outline-model-viewer", OutlineModelViewer);

Binary file not shown.

View File

@ -38,7 +38,8 @@ Welcome to my little home on the web! Below you'll find recent blog posts, proje
<span class="dt-label">Last Modified</span>
</section>
<hr class="heading">
{% for post in site.projects limit:5 %}
{% assign projects = site.projects | sort_natural: "last_modified_at"%}
{% for post in projects limit:5 %}
{% include project_summary.html %}
{% endfor %}
<a href = "/projects/" class = "highlights-more">More</a>