Skip to content

Commit

Permalink
Merge pull request #275 from xyz-tools/poc/instant-color-change
Browse files Browse the repository at this point in the history
Update color instantly
  • Loading branch information
remcoder authored Jan 14, 2025
2 parents edc18ca + ff62254 commit c485237
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/no-unsafe-declaration-merging': 'off'
},
ignorePatterns: ['dist', 'examples'],
ignorePatterns: ['dist', 'examples', 'demo/lib'],
env: {
browser: true
}
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
dist
vue.esm-browser.js
*.min.css
examples/react-typescript-demo/build
examples/react-typescript-demo/build
demo/lib
5 changes: 3 additions & 2 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ <h1 class="text-center m-3 mb-5">GCode Preview
</div>

</section>
<section>

<!-- <section>
<div class="controls">
<label for="highlight">Highlight top layer</label>
<input type="checkbox" v-model="settings.highlightTopLayer" :disabled="!settings.renderExtrusion" />
Expand All @@ -183,8 +184,8 @@ <h1 class="text-center m-3 mb-5">GCode Preview
<input type="color" id="last-segment-color" v-model="settings.lastSegmentColor"
:disabled="!settings.highlightLastSegment || !settings.renderExtrusion" />
</div>
</section> -->

</section>
</div>
</div>
<div class="panel" v-cloak v-show="activeTab == 'build-volume'">
Expand Down
5 changes: 4 additions & 1 deletion demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ export const app = (window.app = createApp({
preview.renderExtrusion = settings.value.renderExtrusion;
preview.renderTubes = settings.value.renderTubes;
preview.extrusionWidth = +settings.value.extrusionWidth;
preview.extrusionColor = settings.value.colors.length === 1 ? settings.value.colors[0] : settings.value.colors;

// TODO: should be a quick update:
preview.topLayerColor = settings.value.highlightTopLayer ? settings.value.topLayerColor : undefined;
Expand All @@ -230,6 +229,10 @@ export const app = (window.app = createApp({
watchEffect(() => {
preview.singleLayerMode = settings.value.singleLayerMode;
});

watchEffect(() => {
preview.extrusionColor = settings.value.colors.length === 1 ? settings.value.colors[0] : settings.value.colors;
});
});

return {
Expand Down
26 changes: 23 additions & 3 deletions demo/js/presets.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const presets = {
original: 'https://www.thingiverse.com/thing:387266'
},
extrusionWidth: 0.5,
extrusionColor: ['rgb(83,209,104)'],
extrusionColor: ['#95dfa1'],
travelColor: 'red',
topLayerColor: undefined,
lastSegmentColor: undefined,
Expand All @@ -47,7 +47,7 @@ export const presets = {
minLayerThreshold: 0.6,
renderExtrusion: true,
renderTubes: true,
extrusionColor: ['rgb(84,74,187)'],
extrusionColor: ['#8782bf'],
renderTravel: true,
travelColor: '#00FF00',
topLayerColor: undefined,
Expand All @@ -71,7 +71,7 @@ export const presets = {
lineWidth: 1,
renderExtrusion: true,
renderTubes: true,
extrusionColor: ['#777777'],
extrusionColor: ['#919191'],
renderTravel: true,
travelColor: '#00FF00',
topLayerColor: '#aaaaaa',
Expand All @@ -81,5 +81,25 @@ export const presets = {
y: 200,
z: 0
}
},
marlin: {
title: 'multicolor Nemo (6MB)',
file: 'https://storage.googleapis.com/gcode-preview.firebasestorage.app/Marlin.gcode',
model: {
name: 'Marlin (multi-material remix)',
designer: 'cipis',
license: 'CC BY-NC-SA',
original: 'https://www.thingiverse.com/thing:387266'
},
extrusionWidth: 0.5,
extrusionColor: ['orange', 'black', 'white'],
travelColor: 'red',
topLayerColor: undefined,
lastSegmentColor: undefined,
buildVolume: {
x: 250,
y: 250,
z: 0
}
}
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"copy-dist": "copyfiles -f dist/gcode-preview.es.js demo/dist",
"deploy:preview": "live-server demo",
"typedoc": "typedoc",
"typedoc:watch": "typedoc --watch"
"typedoc:watch": "typedoc --watch",
"check": "npm run typeCheck && npm run lint && npm run test"
},
"dependencies": {
"lil-gui": "^0.19.2",
Expand Down
76 changes: 43 additions & 33 deletions src/dev-gui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type DevModeOptions = {
*/
class DevGUI {
private gui: GUI;
private watchedObject: WebGLPreview;
private webglPreview;
private options?: DevModeOptions | undefined;
private openFolders: string[] = [];

Expand All @@ -33,8 +33,8 @@ class DevGUI {
* @param watchedObject - The object to monitor and control
* @param options - Configuration options for the GUI
*/
constructor(watchedObject: WebGLPreview, options?: DevModeOptions | undefined) {
this.watchedObject = watchedObject;
constructor(webglPreview: WebGLPreview, options?: DevModeOptions | undefined) {
this.webglPreview = webglPreview;
this.options = options;

this.gui = new GUI();
Expand Down Expand Up @@ -98,7 +98,6 @@ class DevGUI {
.map((folder) => {
return folder._title;
});
console.log(this.openFolders);
localStorage.setItem('dev-gui-open', JSON.stringify({ open: this.openFolders }));
}

Expand All @@ -113,13 +112,17 @@ class DevGUI {
render.onOpenClose(() => {
this.saveOpenFolders();
});
render.add(this.watchedObject.renderer.info.render, 'triangles').listen();
render.add(this.watchedObject.renderer.info.render, 'calls').listen();
render.add(this.watchedObject.renderer.info.render, 'lines').listen();
render.add(this.watchedObject.renderer.info.render, 'points').listen();
render.add(this.watchedObject.renderer.info.memory, 'geometries').listen();
render.add(this.watchedObject.renderer.info.memory, 'textures').listen();
render.add(this.watchedObject, '_lastRenderTime').listen();
render.add(this.webglPreview.renderer.info.render, 'triangles').listen();
render.add(this.webglPreview.renderer.info.render, 'calls').listen();
render.add(this.webglPreview.renderer.info.render, 'lines').listen();
render.add(this.webglPreview.renderer.info.render, 'points').listen();
render.add(this.webglPreview.renderer.info.memory, 'geometries').listen();
render.add(this.webglPreview.renderer.info.memory, 'textures').listen();
render.add(this.webglPreview, '_lastRenderTime').listen();

render.add(this.webglPreview, 'ambientLight', 0, 1, 0.01);
render.add(this.webglPreview, 'directionalLight', 0, 2, 0.1);
render.add(this.webglPreview, 'brightness', 0, 2, 0.1);
}

/**
Expand All @@ -134,14 +137,21 @@ class DevGUI {
this.saveOpenFolders();
});
const cameraPosition = camera.addFolder('Camera position');
cameraPosition.add(this.watchedObject.camera.position, 'x').listen();
cameraPosition.add(this.watchedObject.camera.position, 'y').listen();
cameraPosition.add(this.watchedObject.camera.position, 'z').listen();
cameraPosition.add(this.webglPreview.camera.position, 'x').listen();
cameraPosition.add(this.webglPreview.camera.position, 'y').listen();
cameraPosition.add(this.webglPreview.camera.position, 'z').listen();

// button to save camera position to local storage
// cameraPosition.add({
// saveCameraPosition: () => {
// localStorage.setItem('cameraPosition', JSON.stringify(this.webglPreview.camera.position));
// },
// }, 'saveCameraPosition').name('Save camera position');

const cameraRotation = camera.addFolder('Camera rotation');
cameraRotation.add(this.watchedObject.camera.rotation, 'x').listen();
cameraRotation.add(this.watchedObject.camera.rotation, 'y').listen();
cameraRotation.add(this.watchedObject.camera.rotation, 'z').listen();
cameraRotation.add(this.webglPreview.camera.rotation, 'x').listen();
cameraRotation.add(this.webglPreview.camera.rotation, 'y').listen();
cameraRotation.add(this.webglPreview.camera.rotation, 'z').listen();
}

/**
Expand All @@ -155,18 +165,18 @@ class DevGUI {
parser.onOpenClose(() => {
this.saveOpenFolders();
});
parser.add(this.watchedObject.job.state, 'x').listen();
parser.add(this.watchedObject.job.state, 'y').listen();
parser.add(this.watchedObject.job.state, 'z').listen();
parser.add(this.watchedObject.job.paths, 'length').name('paths.count').listen();
parser.add(this.watchedObject.parser.lines, 'length').name('lines.count').listen();
parser.add(this.webglPreview.job.state, 'x').listen();
parser.add(this.webglPreview.job.state, 'y').listen();
parser.add(this.webglPreview.job.state, 'z').listen();
parser.add(this.webglPreview.job.paths, 'length').name('paths.count').listen();
parser.add(this.webglPreview.parser.lines, 'length').name('lines.count').listen();
}

/**
* Sets up the build volume controls panel with dimension controls
*/
private setupBuildVolumeFolder(): void {
if (!this.watchedObject.buildVolume) {
if (!this.webglPreview.buildVolume) {
return;
}
const buildVolume = this.gui.addFolder('Build Volume');
Expand All @@ -177,28 +187,28 @@ class DevGUI {
this.saveOpenFolders();
});
buildVolume
.add(this.watchedObject.buildVolume, 'x')
.add(this.webglPreview.buildVolume, 'x')
.min(0)
.max(600)
.listen()
.onChange(() => {
this.watchedObject.render();
this.webglPreview.render();
});
buildVolume
.add(this.watchedObject.buildVolume, 'y')
.add(this.webglPreview.buildVolume, 'y')
.min(0)
.max(600)
.listen()
.onChange(() => {
this.watchedObject.render();
this.webglPreview.render();
});
buildVolume
.add(this.watchedObject.buildVolume, 'z')
.add(this.webglPreview.buildVolume, 'z')
.min(0)
.max(600)
.listen()
.onChange(() => {
this.watchedObject.render();
this.webglPreview.render();
});
}

Expand All @@ -214,13 +224,13 @@ class DevGUI {
this.saveOpenFolders();
});
devHelpers
.add(this.watchedObject, '_wireframe')
.add(this.webglPreview, '_wireframe')
.listen()
.onChange(() => {
this.watchedObject.render();
this.webglPreview.render();
});
devHelpers.add(this.watchedObject, 'render').listen();
devHelpers.add(this.watchedObject, 'clear').listen();
devHelpers.add(this.webglPreview, 'render').listen();
devHelpers.add(this.webglPreview, 'clear').listen();
}
}

Expand Down
93 changes: 93 additions & 0 deletions src/helpers/colorMaterial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ShaderMaterial } from 'three/src/materials/ShaderMaterial.js';
import { Color } from 'three/src/math/Color.js';

/*
This file contains a custom ShaderMaterial that calculates the lighting of a mesh based on the normal of the mesh and a fixed light direction.
The material also allows for setting the color, ambient light intensity, directional light intensity, and brightness.
*/

// Vertex Shader
const vertexShader = `
uniform float clipMinY;
uniform float clipMaxY;
varying vec3 vNormal;
varying vec3 vPosition;
varying float vWorldY;
void main() {
vNormal = normalize(normalMatrix * normal);
vPosition = position;
vWorldY = (modelMatrix * vec4(position, 1.0)).y;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

// Fragment Shader
const fragmentShader = `
uniform vec3 uColor;
uniform float ambient;
uniform float directional;
uniform float brightness;
uniform float clipMinY;
uniform float clipMaxY;
varying vec3 vNormal;
varying vec3 vPosition;
varying float vWorldY;
void main() {
// Apply clipping
if (vWorldY < clipMinY || vWorldY > clipMaxY) {
discard;
}
// Fixed light direction (pointing from front-left)
vec3 lightDir = normalize(vec3(-0.8, -0.2, -0.8));
// Calculate diffuse lighting with increased intensity
float diff = max(dot(vNormal, -lightDir), 0.0) * directional;
// Combine lighting with color
vec3 finalColor = uColor * (diff + ambient);
// Add a bit of extra brightness
finalColor = min(finalColor * brightness, 1.0);
gl_FragColor = vec4(finalColor, 1.0);
}
`;

// cachedMaterial is used to store the material so that it is only created once for every color
export const cachedMaterials: { [color: number]: ShaderMaterial } = {};

// TODO: remove the cache or add a way to clear it

export function createColorMaterial(
color: number,
ambient: number,
directional: number,
brightness: number
): ShaderMaterial {
// Check if the material for the given color is already cached
if (cachedMaterials[color]) {
return cachedMaterials[color];
}
console.log('createColorMaterial. not cached');

const material = new ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uColor: { value: new Color(color) },
ambient: { value: ambient },
directional: { value: directional },
brightness: { value: brightness },
clipMinY: { value: -Infinity },
clipMaxY: { value: Infinity }
}
});

// Cache the material
cachedMaterials[color] = material;

return material;
}
Loading

0 comments on commit c485237

Please sign in to comment.