Skip to content

Commit 941cde9

Browse files
committed
Reconcile the rebase with develop's newer work
Rebasing onto develop dropped the two merge commits, and with them the manual resolutions they carried. This restores that work: - scene-manager-properties gets back the orthographic camera coverage added while merging develop's camera feature - renderer-smoke looks up the Extrusions group again - the scene-manager suite stays branch-side, as the old-architecture suite tests render paths that now live in ObjectsManager - the demo drops the unused drawBoundingBox ref It also adopts what develop gained since the last sync: the fallbackExtrusionColor guard stays in renderPaths (a tool index past the configured colors warns once and falls back instead of drawing with undefined), and its tests are ported to the new-architecture suite in scene-manager-properties.
1 parent 18b04b8 commit 941cde9

6 files changed

Lines changed: 279 additions & 786 deletions

File tree

demo/js/app.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const app = (window.app = createApp({
2323
const model = ref(null);
2424
const dragging = ref(false);
2525
const settings = ref(Object.assign({}, defaultSettings));
26-
const drawBoundingBox = ref(false);
2726
const enableDevMode = ref(false);
2827

2928
watch(selectedPreset, (preset) => {
@@ -210,7 +209,6 @@ export const app = (window.app = createApp({
210209
dragging,
211210
settings,
212211
loadProgressive,
213-
drawBoundingBox,
214212
enableDevMode,
215213
selectTab,
216214
addColor,

demo/js/default-settings.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const defaultSettings = {
3030
lastSegmentColor: null,
3131
drawBuildVolume: true,
3232
backgroundColor: '#141414',
33-
orthographic: false,
3433
boundingBoxColor: '#A830F8',
35-
drawBoundingBox: false
34+
drawBoundingBox: false,
35+
orthographic: false
3636
};

src/__tests__/renderer-smoke.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('SceneManager runtime smoke test', () => {
7373
preview.processGCode(SAMPLE_GCODE);
7474
await nextFrame();
7575

76-
const group = preview.sceneManager.scene.getObjectByName('allLayers');
76+
const group = preview.sceneManager.scene.getObjectByName('Extrusions');
7777
expect(group).toBeDefined();
7878
expect(group?.children.length).toBeGreaterThan(0);
7979

src/__tests__/scene-manager-properties.ts

Lines changed: 169 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,41 @@ import { test, expect, describe, vi, beforeEach, afterEach } from 'vitest';
22

33
// WebGL and OrbitControls cannot run under happy-dom, so only those two are stubbed.
44
// Everything else — the scene graph, materials, the ObjectsManager — is real.
5-
vi.mock('three/examples/jsm/controls/OrbitControls.js', () => ({
6-
OrbitControls: class {
7-
target = { set: vi.fn(), x: 0, y: 0, z: 0 };
8-
update = vi.fn();
9-
dispose = vi.fn();
5+
vi.mock('three/examples/jsm/controls/OrbitControls.js', () => {
6+
// the orthographic swap clones and copies the target, so it needs to behave
7+
// like a Vector3 rather than a bag of spies
8+
class Vec3 {
9+
constructor(
10+
public x = 0,
11+
public y = 0,
12+
public z = 0
13+
) {}
14+
set(x: number, y: number, z: number) {
15+
this.x = x;
16+
this.y = y;
17+
this.z = z;
18+
return this;
19+
}
20+
copy(v: { x: number; y: number; z: number }) {
21+
return this.set(v.x, v.y, v.z);
22+
}
23+
clone() {
24+
return new Vec3(this.x, this.y, this.z);
25+
}
26+
toArray() {
27+
return [this.x, this.y, this.z];
28+
}
1029
}
11-
}));
30+
31+
return {
32+
OrbitControls: class {
33+
target = new Vec3();
34+
screenSpacePanning = false;
35+
update = vi.fn();
36+
dispose = vi.fn();
37+
}
38+
};
39+
});
1240

1341
vi.mock('three', async (importOriginal) => {
1442
const actual = await importOriginal<typeof import('three')>();
@@ -34,7 +62,7 @@ import { SceneManager, type SceneManagerOptions } from '../scene-manager';
3462
import { ObjectsManager } from '../objects-manager';
3563
import { Job } from '../job';
3664
import { Path, PathType } from '../path';
37-
import { Color, Group } from 'three';
65+
import { Color, Group, OrthographicCamera, PerspectiveCamera } from 'three';
3866

3967
describe('SceneManager properties', () => {
4068
let sceneManager: SceneManager;
@@ -144,6 +172,43 @@ describe('SceneManager properties', () => {
144172
expect(spy).toHaveBeenCalledWith(expect.anything(), new Color('#ff0000'), 0);
145173
});
146174

175+
test('a tool without a configured color falls back to the last one, warning once', () => {
176+
// a tool index one past the colors supplied used to read extrusionColor[2]
177+
// as undefined and poison the material color downstream
178+
const job = createJob();
179+
const highToolPath = new Path(PathType.Extrusion, 0.6, 0.2, 2);
180+
highToolPath.addPoint(0, 0, 0);
181+
highToolPath.addPoint(5, 0, 0);
182+
job.addPath(highToolPath);
183+
184+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
185+
const fresh = createSceneManager({ job, extrusionColor: ['#00ff00'] });
186+
const spy = vi.spyOn(objectsManager(fresh), 'renderExtrusions');
187+
188+
expect(() => fresh.render()).not.toThrow();
189+
190+
expect(spy).toHaveBeenCalledWith(expect.anything(), new Color('#00ff00'), 2);
191+
expect(warn).toHaveBeenCalledWith('No extrusionColor configured for tool index 2, falling back to another color');
192+
193+
fresh.render();
194+
expect(warn).toHaveBeenCalledTimes(1);
195+
196+
fresh.dispose();
197+
});
198+
199+
test('falls back to the default extrusion color when the color array is empty', () => {
200+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
201+
const fresh = createSceneManager({ job: createJob(), extrusionColor: [] });
202+
const spy = vi.spyOn(objectsManager(fresh), 'renderExtrusions');
203+
204+
expect(() => fresh.render()).not.toThrow();
205+
206+
expect(spy).toHaveBeenCalledWith(expect.anything(), SceneManager.defaultExtrusionColor, 0);
207+
208+
fresh.dispose();
209+
warn.mockRestore();
210+
});
211+
147212
test('extrusionColor accepts an array, one entry per tool', () => {
148213
const spy = vi.spyOn(objectsManager(sceneManager), 'setExtrusionColor');
149214

@@ -420,6 +485,103 @@ describe('SceneManager properties', () => {
420485
});
421486
});
422487

488+
describe('orthographic camera', () => {
489+
test('starts perspective', () => {
490+
expect(sceneManager.orthographic).toBe(false);
491+
expect(sceneManager.camera).toBeInstanceOf(PerspectiveCamera);
492+
});
493+
494+
test('swaps the camera when enabled', () => {
495+
sceneManager.orthographic = true;
496+
497+
expect(sceneManager.orthographic).toBe(true);
498+
expect(sceneManager.camera).toBeInstanceOf(OrthographicCamera);
499+
});
500+
501+
test('swaps back when disabled', () => {
502+
sceneManager.orthographic = true;
503+
sceneManager.orthographic = false;
504+
505+
expect(sceneManager.camera).toBeInstanceOf(PerspectiveCamera);
506+
});
507+
508+
test('carries the camera position and target across the swap', () => {
509+
sceneManager.camera.position.set(11, 22, 33);
510+
sceneManager.controls.target.set(1, 2, 3);
511+
512+
sceneManager.orthographic = true;
513+
514+
expect(sceneManager.camera.position.toArray()).toEqual([11, 22, 33]);
515+
expect(sceneManager.controls.target.toArray()).toEqual([1, 2, 3]);
516+
});
517+
518+
test('enables screen space panning in orthographic mode', () => {
519+
sceneManager.orthographic = true;
520+
521+
expect(sceneManager.controls.screenSpacePanning).toBe(true);
522+
});
523+
524+
test('ignores a repeated value', () => {
525+
sceneManager.orthographic = true;
526+
const camera = sceneManager.camera;
527+
528+
sceneManager.orthographic = true;
529+
530+
expect(sceneManager.camera).toBe(camera);
531+
});
532+
533+
test('sizes the frustum from the job bounds', () => {
534+
sceneManager.orthographic = true;
535+
536+
// the test job spans 10mm, so the frustum is padded around that
537+
const camera = sceneManager.camera as OrthographicCamera;
538+
expect(camera.top).toBeGreaterThan(0);
539+
expect(camera.left).toBeLessThan(0);
540+
});
541+
542+
test('falls back to the build volume when the job has no bounds', () => {
543+
const job = new Job();
544+
appendPath(job, PathType.Extrusion, [
545+
[0, 0, 0],
546+
[10, 0, 0]
547+
]);
548+
const unbounded = createSceneManager({ job });
549+
550+
unbounded.orthographic = true;
551+
552+
const camera = unbounded.camera as OrthographicCamera;
553+
// build volume is 200mm, much larger than the job
554+
expect(camera.top).toBeGreaterThan(50);
555+
unbounded.dispose();
556+
});
557+
558+
test('falls back to a default size with neither bounds nor build volume', () => {
559+
const job = new Job();
560+
appendPath(job, PathType.Extrusion, [
561+
[0, 0, 0],
562+
[10, 0, 0]
563+
]);
564+
const bare = createSceneManager({ job });
565+
bare.buildVolume = undefined;
566+
567+
bare.orthographic = true;
568+
569+
expect(bare.camera).toBeInstanceOf(OrthographicCamera);
570+
bare.dispose();
571+
});
572+
573+
test('resize keeps the orthographic frustum square to the canvas', () => {
574+
sceneManager.orthographic = true;
575+
const camera = sceneManager.camera as OrthographicCamera;
576+
const widthBefore = camera.right - camera.left;
577+
578+
sceneManager.resize();
579+
580+
expect(camera.right - camera.left).toBeCloseTo(widthBefore, 5);
581+
expect(camera.right).toBeGreaterThan(camera.top);
582+
});
583+
});
584+
423585
describe('progressive rendering', () => {
424586
test('renders every path across frames', async () => {
425587
sceneManager.clear();
@@ -542,7 +704,6 @@ describe('SceneManager properties', () => {
542704
travelColor: '#404040',
543705
topLayerColor: '#505050',
544706
lastSegmentColor: '#606060',
545-
toolColors: { 0: '#707070' },
546707
disableGradient: true,
547708
renderTubes: true,
548709
renderTravel: true,

0 commit comments

Comments
 (0)