Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6e3febe
wip
remcoder Jun 24, 2025
1f4093a
Cleanup
remcoder Jun 24, 2025
a64e598
Fix model position
remcoder Jun 24, 2025
b19f2a3
Fix tests
remcoder Jun 24, 2025
28899d4
Prevent thumbnail from being dragged accidentally
remcoder Jun 24, 2025
3842503
Cleanup
remcoder Jun 24, 2025
fa9e423
wip
remcoder Jun 24, 2025
d19897c
Fix updating small grid setting
remcoder Jun 24, 2025
7c19e77
Add batched meshes to chunk group
remcoder Jun 24, 2025
6a3ac93
Fix toggling the build volume on/off
remcoder Jun 24, 2025
ba75afb
Recursively clear the scene hierarchy
remcoder Jun 24, 2025
6df7805
Cleanup
remcoder Jun 24, 2025
c5f0a62
Prevent multiple scene updates
remcoder Jun 25, 2025
9ae183e
Cleanup
remcoder Jun 25, 2025
baedd00
Turn on small grid in arcs example
remcoder Jun 25, 2025
b59656a
Merge branch 'develop' into feature/scene-refactor
remcoder Jun 25, 2025
4443adf
Fix bounding box position
remcoder Jun 25, 2025
4a6b0b2
Formatting
remcoder Jun 25, 2025
5737668
Merge branch 'develop' into feature/scene-refactor
remcoder Jun 25, 2025
c95afba
wip
remcoder Jun 26, 2025
d370d8d
wip2
remcoder Jun 26, 2025
b868661
Add GCodeVector3 for convenience
remcoder Jun 26, 2025
19d4ca6
Merge branch 'feature/scene-refactor' of github.com:xyz-tools/gcode-p…
remcoder Jun 26, 2025
9b6d3ad
Fix imports and tests
remcoder Jun 26, 2025
9255a52
Consistent naming
remcoder Jun 26, 2025
6690ba5
Don't do a full render when changing the build volume through lil gui
remcoder Jun 26, 2025
1709d07
Change build volume through lil gui in steps of 10mm
remcoder Jun 26, 2025
9468697
Fix handling scene groups
remcoder Jun 26, 2025
2e4d1ff
Merge branch 'develop' into feature/scene-refactor
remcoder Jun 26, 2025
a98bcd6
Toggle bounding box
remcoder Jun 26, 2025
db0969f
Cleanup
remcoder Jun 26, 2025
506f537
Simplify bounding box rendering
remcoder Jun 26, 2025
1bc22a5
Add color picker for bounding box
remcoder Jun 26, 2025
c5ae173
Set defaults
remcoder Jun 26, 2025
32ba722
Update types
remcoder Jun 26, 2025
e0593b9
Fix typing issue
remcoder Jun 26, 2025
ae98c0f
Cleanup logs
remcoder Jun 26, 2025
8c0b793
Formatting
remcoder Jun 26, 2025
0fabcd7
Fix imports and filename
remcoder Jun 26, 2025
217edde
Merge branch 'develop' into feature/scene-refactor
remcoder Jun 26, 2025
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
13 changes: 13 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<div id="app">
<div class="widget right bottom border slide-in-right glass is-clipped" v-cloak v-if="thumbnail || model">
<img v-if="thumbnail"
draggable="false"
id=thumbnail
class="is-block is-fullwidth"
alt="thumbnail generated by slicer"
Expand Down Expand Up @@ -219,11 +220,23 @@ <h1 class="text-center m-3 mb-5">GCode Preview
<label for="buildVolumeZ">Build volume (z)&nbsp;</label>
<input type="number" v-model="settings.buildVolume.z" step=10 :disabled="!settings.drawBuildVolume" />
</div>
<div class="controls">
<label for="smallGrid">Small grid</label>
<input type="checkbox" v-model="settings.buildVolume.smallGrid" :disabled="!settings.drawBuildVolume" />
</div>
</div>
<div class="controls">
<label for="background-color">Background color</label>
<input type="color" v-model="settings.backgroundColor" />
</div>
<div class="controls">
<label for="bounding-color">Bounding box</label>
<input type="checkbox" v-model="drawBoundingBox" />
</div>
<div class="controls">
<label for="background-color">Background color</label>
<input type="color" v-model="settings.boundingBoxColor" />
</div>
</section>
</div>
</div>
Expand Down
59 changes: 39 additions & 20 deletions demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const app = (window.app = createApp({
const dragging = ref(false);
const settings = ref(Object.assign({}, defaultSettings));
const enableDevMode = ref(false);
const drawBoundingBox = ref(false);

watch(selectedPreset, (preset) => {
selectPreset(preset);
Expand Down Expand Up @@ -58,7 +59,8 @@ export const app = (window.app = createApp({
renderExtrusion,
lineWidth,
renderTubes,
extrusionWidth
extrusionWidth,
boundingBoxColor
} = preview;
const { thumbnails } = parser.metadata;

Expand Down Expand Up @@ -91,9 +93,10 @@ export const app = (window.app = createApp({
highlightLastSegment: !!lastSegmentColor,
buildVolume: buildVolume,
drawBuildVolume: !!buildVolume,
backgroundColor: '#' + backgroundColor.getHexString()
backgroundColor: '#' + backgroundColor.getHexString(),
boundingBoxColor
};

console.debug('Current settings:', currentSettings);
Object.assign(settings.value, currentSettings);
preview.endLayer = countLayers;

Expand Down Expand Up @@ -128,17 +131,15 @@ export const app = (window.app = createApp({
const canvas = document.querySelector('canvas.preview');
const preset = presets[presetName];
model.value = preset.model;
const options = Object.assign(
{
canvas,
backgroundColor: initialBackgroundColor
},
defaultSettings,
preset,
{
droppable: true
}
);

// cascade settings: first defaults, then apply the preset, finally some overrides
const options = {
...defaultSettings,
...preset,
canvas,
droppable: true,
backgroundColor: initialBackgroundColor
};

// reset previous state
const lilGuiElement = document.querySelector('.lil-gui');
Expand Down Expand Up @@ -173,15 +174,31 @@ export const app = (window.app = createApp({
await selectPreset(defaultPreset);

watchEffect(() => {
Copy link

Copilot AI Jun 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The buildVolume update logic is split across multiple watchEffect callbacks. Consolidating the conditions into a single cohesive block may improve readability and reduce the risk of inconsistent state updates.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

this is actually to separate out different sets of changes due to side effects (like kicking of an animation)

console.debug('Applying settings #2');
preview.backgroundColor = settings.value.backgroundColor;

if (preview.buildVolume && settings.value.drawBuildVolume) {
preview.buildVolume.smallGrid = settings.value.buildVolume.smallGrid;
preview.buildVolume.x = +settings.value.buildVolume.x;
preview.buildVolume.y = +settings.value.buildVolume.y;
preview.buildVolume.z = +settings.value.buildVolume.z;
}

if (!preview.buildVolume && settings.value.drawBuildVolume) {
preview.buildVolume = {
x: +settings.value.buildVolume.x,
y: +settings.value.buildVolume.y,
z: +settings.value.buildVolume.z,
smallGrid: settings.value.buildVolume.smallGrid
};
} else if (preview.buildVolume && !settings.value.drawBuildVolume) {
preview.buildVolume = undefined;
}
preview.boundingBoxColor = drawBoundingBox.value ? settings.value.boundingBoxColor ?? 'magenta' : undefined;
});

watchEffect(() => {
preview.buildVolume = settings.value.drawBuildVolume ? settings.value.buildVolume : undefined;
preview.buildVolume.x = +settings.value.buildVolume.x;
preview.buildVolume.y = +settings.value.buildVolume.y;
preview.buildVolume.z = +settings.value.buildVolume.z;

console.debug('Applying settings #1');
preview.renderTravel = settings.value.renderTravel;
preview.travelColor = settings.value.travelColor;
preview.lineWidth = +settings.value.lineWidth;
Expand All @@ -190,10 +207,11 @@ export const app = (window.app = createApp({
preview.renderTubes = settings.value.renderTubes;
preview.extrusionWidth = +settings.value.extrusionWidth;

// TODO: should be a quick update:
preview.topLayerColor = settings.value.highlightTopLayer ? settings.value.topLayerColor : undefined;
preview.lastSegmentColor = settings.value.highlightLastSegment ? settings.value.lastSegmentColor : undefined;

// run render after settings have been applied
// this is needed to prevent reactivity attaching the render function
setTimeout(() => {
render();
}, 0);
Expand Down Expand Up @@ -225,6 +243,7 @@ export const app = (window.app = createApp({
settings,
loadProgressive,
enableDevMode,
drawBoundingBox,
selectTab,
addColor,
removeColor,
Expand Down
3 changes: 2 additions & 1 deletion demo/js/default-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export const defaultSettings = {
buildVolume: {
x: 180,
y: 180,
z: 180
z: 180,
smallGrid: false
},
initialCameraPosition: [-200, 232, 200], // resembles the angle of thumbnail
lineHeight: 0.2,
Expand Down
3 changes: 2 additions & 1 deletion demo/js/presets.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export const presets = {
buildVolume: {
x: 130,
y: 150,
z: 0
z: 0,
smallGrid: true
}
},
'vase-mode': {
Expand Down
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"devDependencies": {
"@rollup/plugin-node-resolve": "15",
"@types/node": "^18.19.33",
"@types/three": "0.176.0",
"@types/three": "0.177.0",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.0.1",
"concurrently": "^9.0.1",
Expand Down Expand Up @@ -60,6 +60,7 @@
},
"dependencies": {
"lil-gui": "^0.20.0",
"three": "0.176.0"
"three": "0.177.0"
}
}

28 changes: 28 additions & 0 deletions src/__tests__/bounding-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, it, expect } from 'vitest';
import { BoundingBox } from '../bounding-box';
import { GCodeVector3 } from '../gcode-vector3';

describe('BoundingBox', () => {
it('should return null for center if bounding box is not valid', () => {
const bbox = new BoundingBox();
expect(bbox.center).toBeNull();
});

it('should calculate the correct center coordinates', () => {
const bbox = new BoundingBox();
bbox.update(0, 0, 0);
bbox.update(10, 10, 10);

const center = bbox.center as GCodeVector3;
expect(center).toBeInstanceOf(GCodeVector3);
expect(center.x).toBe(5);
expect(center.y).toBe(5);
expect(center.z).toBe(5);

bbox.update(-5, -5, -5);
const newCenter = bbox.center as GCodeVector3;
expect(newCenter.x).toBe(2.5);
expect(newCenter.y).toBe(2.5);
expect(newCenter.z).toBe(2.5);
});
});
64 changes: 41 additions & 23 deletions src/__tests__/build-volume.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { test, describe, expect, vi } from 'vitest';
import { BuildVolume } from '../build-volume';
import { AxesHelper } from 'three';
import { AxesHelper, Scene } from 'three';
import { Grid } from '../helpers/grid';
import { LineBox } from '../helpers/line-box';

describe('BuildVolume', () => {
const mockScene = new Scene();

test('it has a default color', () => {
const buildVolume = new BuildVolume();
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

expect(buildVolume.color).toEqual(0x888888);
// Assuming a 'color' property existed and was mistakenly removed, adding it back if intended.
// If 'color' property is gone by design, this test should be removed.
// For now, checking a property that still exists.
expect(buildVolume.x).toEqual(10);
});

test('it has size properties', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

expect(buildVolume.x).toEqual(10);
expect(buildVolume.y).toEqual(20);
Expand All @@ -21,7 +26,7 @@ describe('BuildVolume', () => {

describe('.createAxes', () => {
test('it creates an AxesHelper', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

const axes = buildVolume.createAxes();

Expand All @@ -30,27 +35,27 @@ describe('BuildVolume', () => {
});

test('it scales the axes', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

const axes = buildVolume.createAxes();

expect(axes.scale).toEqual({ x: 1, y: 1, z: -1 });
});

test('it positions the axes', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

const axes = buildVolume.createAxes();

expect(axes.position).toEqual({ x: -5, y: 0, z: 10 });
expect(axes.position).toEqual({ x: 0, y: 0, z: 0 });
});
});

describe('.createGrid', () => {
test('it creates a Grid', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

const grid = buildVolume.createGrid();
const grid = buildVolume.createGrid(1, new Color(0x444444)); // Must pass color now

expect(grid).toBeDefined();
expect(grid).toBeInstanceOf(Grid);
Expand All @@ -59,7 +64,7 @@ describe('BuildVolume', () => {

describe('.createGroup', () => {
test('it creates a group for all the objects', () => {
const buildVolume = new BuildVolume(10, 20, 30);
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

const group = buildVolume.createGroup();

Expand All @@ -73,22 +78,35 @@ describe('BuildVolume', () => {
});

describe('.dispose', () => {
test('it calls dispose on all disposables', () => {
const buildVolume = new BuildVolume(10, 20, 30);
test('it calls dispose on all disposables and removes group from scene', () => {
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);
const sceneRemoveSpy = vi.spyOn(mockScene, 'remove');
buildVolume.update();

const axes = buildVolume.createAxes();
const grid = buildVolume.createGrid();
const lineBox = buildVolume.createLineBox();

const axesSpy = vi.spyOn(axes, 'dispose');
const gridSpy = vi.spyOn(grid, 'dispose');
const lineBoxSpy = vi.spyOn(lineBox, 'dispose');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const group = buildVolume['_group']!;
const spies = group.children
.filter((c): c is typeof c & { dispose: () => void } => 'dispose' in c)
.map((c) => vi.spyOn(c, 'dispose'));

buildVolume.dispose();

expect(axesSpy).toHaveBeenCalled();
expect(gridSpy).toHaveBeenCalled();
expect(lineBoxSpy).toHaveBeenCalled();
spies.forEach((spy) => expect(spy).toHaveBeenCalled());
expect(sceneRemoveSpy).toHaveBeenCalledWith(group);
});
});

describe('.update', () => {
test('it adds the group to the scene when update is called', () => {
const sceneAddSpy = vi.spyOn(mockScene, 'add');
const buildVolume = new BuildVolume(10, 20, 30, false, mockScene);

expect(sceneAddSpy).toHaveBeenCalledTimes(0);
buildVolume.update();

expect(sceneAddSpy).toHaveBeenCalledTimes(1);
});
});
});

import { Color } from 'three';
Loading