Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update color instantly #275

Merged
merged 19 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful with changing watchedObject. I had issues with this referencing some old objects when they are replaced.

We have many this.an_object = new ObjectClass() that made it a challenge to watch on the GUI.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads-up. I doublechecked and I don't think this changes breaks anything. The watchedObject is still the same instance but now it is typed to make the compiler happy.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked with the other places where watchedObject is changed too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check it again! Will try tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked all the dev helpers. Nothing is broken by this PR.

The Job info and _lastRenderTime don't update but I checked that was already the case be for this PR.

Only the types have changed and the namei of the instance but there shouldn't be even a change in the javascript code that is being generated.

btw, did you see you can now change the lighting through the dev helper?

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
Loading