From 6cfc6ae01bd11d08c7feb4531a89ca32de06656e Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Tue, 23 Jun 2026 15:44:36 -0400 Subject: [PATCH] fix(export): stamp openable only when an open clip bakes A door/window only carries extras.openable + extras.clips when an open animation actually bakes. Cased openings (no leaf) and fixed windows (no operable sash) are no longer mislabelled openable, so the GLB never claims a part opens when nothing moves. /viewer is unaffected (it already required openable && clips). Adds a regression test for the no-clip case. (Export barrels reordered by the formatter.) Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/editor/src/index.tsx | 2 +- packages/editor/src/lib/glb-export.test.ts | 32 ++++++++++++++++++++++ packages/editor/src/lib/glb-export.ts | 9 ++++-- packages/viewer/src/index.ts | 2 +- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index 39b8bbdb..0369928c 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -218,7 +218,6 @@ export { resolveCeilingPlanPointSnap, } from './lib/ceiling-plan-snap' export { EDITOR_LAYER } from './lib/constants' -export { exportSceneToGlb } from './lib/glb-export' // Helper libs used by the kind-owned roof / stair / elevator panels. export { resolveCurrentBuildingId, @@ -247,6 +246,7 @@ export { getFloorplanWallThickness, } from './lib/floorplan' export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement' +export { exportSceneToGlb } from './lib/glb-export' export { buildResetSurfaceMaterialUpdates, buildRoofSurfaceMaterialPatch, diff --git a/packages/editor/src/lib/glb-export.test.ts b/packages/editor/src/lib/glb-export.test.ts index b7d7c988..74b24f70 100644 --- a/packages/editor/src/lib/glb-export.test.ts +++ b/packages/editor/src/lib/glb-export.test.ts @@ -146,6 +146,38 @@ describe('prepareSceneForExport', () => { expect(leafMarkerSurvived).toBe(false) }) + test('does not flag a door/window openable when no open clip bakes', () => { + // A cased opening (no swing leaf) / fixed window (no operable sash) builds + // no movable part, so no clip bakes and the node must not claim openable. + const root = new THREE.Group() + const openingGroup = new THREE.Group() + openingGroup.add(meshWithNodeMaterial(nodeMaterial())) + root.add(openingGroup) + + const openingId = 'door_opening' + sceneRegistry.nodes.set(openingId, openingGroup) + const nodes: Record = { + [openingId]: { + object: 'node', + id: openingId, + type: 'door', + name: 'Cased opening', + } as unknown as AnyNode, + } + + const { scene, animations } = prepareSceneForExport(root, nodes) + + expect(animations).toHaveLength(0) + const exported = scene.getObjectByProperty('name', openingId) + expect(exported?.userData).toEqual({ + pascalId: openingId, + kind: 'door', + label: 'Cased opening', + }) + expect(exported?.userData.openable).toBeUndefined() + expect(exported?.userData.clips).toBeUndefined() + }) + test('keeps the zone identity node with its polygon and strips the fill mesh', () => { const root = new THREE.Group() const zoneGroup = new THREE.Group() diff --git a/packages/editor/src/lib/glb-export.ts b/packages/editor/src/lib/glb-export.ts index 055afe46..e0a731a3 100644 --- a/packages/editor/src/lib/glb-export.ts +++ b/packages/editor/src/lib/glb-export.ts @@ -497,10 +497,15 @@ function stampIdentity( extras.label = getLevelDisplayName(node as LevelNode) target.visible = true } + // Only doors/windows that actually baked an open clip are openable. A cased + // opening (no leaf) or a fixed window (no operable sash) produces no clip, so + // it stays unflagged — the file never claims a part opens when nothing moves. if (node.type === 'door' || node.type === 'window') { - extras.openable = true const clipNames = clipNamesByNode.get(id) - if (clipNames) extras.clips = clipNames + if (clipNames?.length) { + extras.openable = true + extras.clips = clipNames + } } if (node.type === 'zone') { // Zone fills are stripped from the bake; /viewer rebuilds the room from diff --git a/packages/viewer/src/index.ts b/packages/viewer/src/index.ts index f5b9960c..f9b1eebf 100644 --- a/packages/viewer/src/index.ts +++ b/packages/viewer/src/index.ts @@ -14,10 +14,10 @@ export { ErrorBoundary } from './components/error-boundary' export { NodeRenderer } from './components/renderers/node-renderer' export { default as Viewer, type ViewerHandle } from './components/viewer' export { - GlbScene, type GlbHover, type GlbIdentity, type GlbLevel, + GlbScene, } from './components/viewer/glb-scene' export type { HoverStyle, HoverStyles } from './components/viewer/post-processing' export {