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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0629c7d3e3
commit
6cfc6ae01b
@@ -218,7 +218,6 @@ export {
|
|||||||
resolveCeilingPlanPointSnap,
|
resolveCeilingPlanPointSnap,
|
||||||
} from './lib/ceiling-plan-snap'
|
} from './lib/ceiling-plan-snap'
|
||||||
export { EDITOR_LAYER } from './lib/constants'
|
export { EDITOR_LAYER } from './lib/constants'
|
||||||
export { exportSceneToGlb } from './lib/glb-export'
|
|
||||||
// Helper libs used by the kind-owned roof / stair / elevator panels.
|
// Helper libs used by the kind-owned roof / stair / elevator panels.
|
||||||
export {
|
export {
|
||||||
resolveCurrentBuildingId,
|
resolveCurrentBuildingId,
|
||||||
@@ -247,6 +246,7 @@ export {
|
|||||||
getFloorplanWallThickness,
|
getFloorplanWallThickness,
|
||||||
} from './lib/floorplan'
|
} from './lib/floorplan'
|
||||||
export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement'
|
export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement'
|
||||||
|
export { exportSceneToGlb } from './lib/glb-export'
|
||||||
export {
|
export {
|
||||||
buildResetSurfaceMaterialUpdates,
|
buildResetSurfaceMaterialUpdates,
|
||||||
buildRoofSurfaceMaterialPatch,
|
buildRoofSurfaceMaterialPatch,
|
||||||
|
|||||||
@@ -146,6 +146,38 @@ describe('prepareSceneForExport', () => {
|
|||||||
expect(leafMarkerSurvived).toBe(false)
|
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<string, AnyNode> = {
|
||||||
|
[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', () => {
|
test('keeps the zone identity node with its polygon and strips the fill mesh', () => {
|
||||||
const root = new THREE.Group()
|
const root = new THREE.Group()
|
||||||
const zoneGroup = new THREE.Group()
|
const zoneGroup = new THREE.Group()
|
||||||
|
|||||||
@@ -497,10 +497,15 @@ function stampIdentity(
|
|||||||
extras.label = getLevelDisplayName(node as LevelNode)
|
extras.label = getLevelDisplayName(node as LevelNode)
|
||||||
target.visible = true
|
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') {
|
if (node.type === 'door' || node.type === 'window') {
|
||||||
extras.openable = true
|
|
||||||
const clipNames = clipNamesByNode.get(id)
|
const clipNames = clipNamesByNode.get(id)
|
||||||
if (clipNames) extras.clips = clipNames
|
if (clipNames?.length) {
|
||||||
|
extras.openable = true
|
||||||
|
extras.clips = clipNames
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (node.type === 'zone') {
|
if (node.type === 'zone') {
|
||||||
// Zone fills are stripped from the bake; /viewer rebuilds the room from
|
// Zone fills are stripped from the bake; /viewer rebuilds the room from
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export { ErrorBoundary } from './components/error-boundary'
|
|||||||
export { NodeRenderer } from './components/renderers/node-renderer'
|
export { NodeRenderer } from './components/renderers/node-renderer'
|
||||||
export { default as Viewer, type ViewerHandle } from './components/viewer'
|
export { default as Viewer, type ViewerHandle } from './components/viewer'
|
||||||
export {
|
export {
|
||||||
GlbScene,
|
|
||||||
type GlbHover,
|
type GlbHover,
|
||||||
type GlbIdentity,
|
type GlbIdentity,
|
||||||
type GlbLevel,
|
type GlbLevel,
|
||||||
|
GlbScene,
|
||||||
} from './components/viewer/glb-scene'
|
} from './components/viewer/glb-scene'
|
||||||
export type { HoverStyle, HoverStyles } from './components/viewer/post-processing'
|
export type { HoverStyle, HoverStyles } from './components/viewer/post-processing'
|
||||||
export {
|
export {
|
||||||
|
|||||||
Reference in New Issue
Block a user