From 957f686295c5f5d656fcfd9c149259fa2a49c15b Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Wed, 3 Jun 2026 15:50:30 -0400 Subject: [PATCH 1/4] fix(nodes,viewer): window preset live wall preview + shaped-cutout move tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MoveWindowTool marked the moving window `isTransient` unconditionally, but WindowSystem only rebuilds the host wall's cutout for non-transient windows — so a window *preset* (isNew) showed no live hole on the wall and couldn't be placed consecutively without leaving/re-entering. Guard the transient mark on `!isNew`, matching MoveDoorTool. Separately, shaped openings (arch / rounded / `opening`) rebuild their cutout brush from `node.position`, which a same-wall move doesn't write (it mutates the mesh directly and publishes to `useLiveTransforms`). The wall-system's `getEffectiveNode` only merges `useLiveNodeOverrides` (resize arrows), so shaped cutouts lagged the move while rectangular ones (rebuilt from the live mesh matrixWorld) tracked. Fold `useLiveTransforms` into door/window children before collecting cutouts so shaped holes follow the live move too. Co-Authored-By: Claude Opus 4.8 --- packages/nodes/src/window/move-tool.tsx | 17 +++++++++++------ .../viewer/src/systems/wall/wall-system.tsx | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/nodes/src/window/move-tool.tsx b/packages/nodes/src/window/move-tool.tsx index bbb0504ec..fa9918ba3 100644 --- a/packages/nodes/src/window/move-tool.tsx +++ b/packages/nodes/src/window/move-tool.tsx @@ -70,12 +70,17 @@ const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWindowNode metadata: movingWindowNode.metadata, } - // Mark the moving window as transient so it doesn't intercept wall raycasts while repositioning. - // Without this, duplicates can block `wall:*` events which breaks the cursor box and can cause - // rapid enter/leave churn (triggering expensive wall CSG rebuilds). - useScene.getState().updateNode(movingWindowNode.id, { - metadata: { ...meta, isTransient: true }, - }) + // In move mode (existing window) mark it transient so its mesh skips the live wall CSG + // rebuild while repositioning — the editor requests a final rebuild on commit. For a new + // placement (preset/duplicate) we must NOT mark it transient: WindowSystem only rebuilds + // the host wall's cutout for non-transient windows, so a transient draft shows no live + // preview on the wall and can't be placed consecutively without leaving/re-entering. This + // mirrors MoveDoorTool. + if (!isNew) { + useScene.getState().updateNode(movingWindowNode.id, { + metadata: { ...meta, isTransient: true }, + }) + } let currentWallId: string | null = movingWindowNode.parentId diff --git a/packages/viewer/src/systems/wall/wall-system.tsx b/packages/viewer/src/systems/wall/wall-system.tsx index de20be1e3..912f885cb 100644 --- a/packages/viewer/src/systems/wall/wall-system.tsx +++ b/packages/viewer/src/systems/wall/wall-system.tsx @@ -18,6 +18,7 @@ import { sceneRegistry, spatialGridManager, useLiveNodeOverrides, + useLiveTransforms, useScene, type WallMiterData, type WallNode, @@ -492,9 +493,18 @@ function updateWallGeometry(wallId: string, miterData: WallMiterData) { const childrenNodes = childrenIds .map((childId) => nodes[childId]) .filter((n): n is AnyNode => n !== undefined) - .map((child) => - child.type === 'door' || child.type === 'window' ? getEffectiveNode(child) : child, - ) + .map((child) => { + if (child.type !== 'door' && child.type !== 'window') return child + // `getEffectiveNode` folds in resize overrides (width/height arrows). + // Position moves publish to `useLiveTransforms` instead, so fold that + // in too — otherwise shaped openings (arch/rounded/`opening`), whose + // cutout brush is rebuilt from `node.position`, lag the live move + // (rectangular cutouts already track via the live mesh matrixWorld). + const effective = getEffectiveNode(child) + const live = useLiveTransforms.getState().get(child.id) + if (!live?.position) return effective + return { ...effective, position: live.position } + }) const newGeo = generateExtrudedWall(node, childrenNodes, miterData, slabElevation) From 2f8f83b307f86678e46d9aed1deb562e79cc925b Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Wed, 3 Jun 2026 15:50:41 -0400 Subject: [PATCH 2/4] fix(editor,viewer,nodes): smooth item placement on shelves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues made hosting an item on a shelf janky (item-on-item was already smooth because plain items have no `def.geometry` to rebuild): - Jitter: the draft mesh intercepted the cursor ray, so the shelf-row hit was re-derived from the moving item each frame. Disable raycasting on the draft during placement (incl. async GLB children, reconciled per frame) so the ray passes through to the surface beneath — mirrors MoveRegistryNodeTool. - Reparent/vanish at the edges: `onShelfLeave` flipped state to floor without reparenting the draft off the shelf, so the floor strategy's level-local position rendered compounded with the shelf transform. The grid handler now owns the shelf→floor transition. - In/out oscillation: reparenting the draft onto the shelf dirtied the shelf, and GeometrySystem disposed+rebuilt its boards, making r3f fire a spurious shelf:leave→enter that thrashed placement between the row and the floor. Add an opt-in `def.geometryKey` so GeometrySystem skips the rebuild when geometry inputs are unchanged (shelf boards don't depend on hosted children). Keep the item sticky by testing the cursor ray against the shelf's bounding box: a ray that slips through a gap and lands on the floor behind the shelf still counts as "on the shelf"; only a ray that misses the shelf detaches to the floor. Co-Authored-By: Claude Opus 4.8 --- packages/core/src/registry/types.ts | 14 ++ .../tools/item/use-placement-coordinator.tsx | 141 ++++++++++++++++-- packages/nodes/src/shelf/definition.ts | 21 +++ .../src/systems/geometry/geometry-system.tsx | 21 ++- 4 files changed, 187 insertions(+), 10 deletions(-) diff --git a/packages/core/src/registry/types.ts b/packages/core/src/registry/types.ts index b498bd9fd..cc8a792ee 100644 --- a/packages/core/src/registry/types.ts +++ b/packages/core/src/registry/types.ts @@ -675,6 +675,20 @@ export type NodeDefinition> = { * work (animations, named-mesh material poking). */ geometry?: (node: z.infer, ctx: GeometryContext) => Object3D + /** + * Optional cache key over the geometry-relevant inputs of `node`. When + * set, `` skips the rebuild (dispose + re-create the + * group's children) if the key is unchanged since the last build for + * this node — even though the node was marked dirty. Use for kinds whose + * geometry depends *only* on their own fields (not on `children`, + * `position`, neighbours, or `ctx`): a hosted child reparenting onto a + * shelf, say, dirties the shelf but doesn't change its boards, so without + * this the boards needlessly remount and any pointer hover churns + * (enter/leave) as the meshes are swapped. Must NOT be set for kinds with + * neighbour-dependent geometry (e.g. wall/fence miters via `ctx`), whose + * inputs aren't captured by the node alone. + */ + geometryKey?: (node: z.infer) => string /** * Level-batch precompute hook. Called by `` once per * level per frame, **before** the per-node `def.geometry` calls in diff --git a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx index 3b90fb6e4..8df958c78 100644 --- a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx +++ b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx @@ -18,15 +18,19 @@ import { } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { Html } from '@react-three/drei' -import { useFrame } from '@react-three/fiber' +import { useFrame, useThree } from '@react-three/fiber' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { + Box3, Euler, type Group, type LineSegments, + Matrix4, type Mesh, + type Object3D, PlaneGeometry, Quaternion, + Ray, Vector3, } from 'three' import { distance, smoothstep, uv, vec2 } from 'three/tsl' @@ -196,8 +200,24 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // the lerp on this flag keeps 3D placement smooth without hijacking // 2D drags that share the same draft. const has3DPointerDrivenMoveRef = useRef(false) + // The draft mesh's raycast is disabled while placing so the cursor ray + // passes through it to the surface beneath (grid / item / shelf). Without + // this, the ray hits the moving draft first and the surface strategy keeps + // re-deriving the host point from the draft's own (just-moved) geometry — + // on a multi-row shelf this oscillates the chosen row, jittering the item. + // Mirrors MoveRegistryNodeTool. Reconciled per-frame (the draft mesh can be + // recreated mid-session) and restored on unmount. + const raycastDisabledMeshRef = useRef(null) + const restoreRaycastsRef = useRef void>>([]) + const raycastDisabledChildrenRef = useRef(new WeakSet()) const [dimensionBounds, setDimensionBounds] = useState(null) + // Live camera ref — the shelf-stickiness test reconstructs the cursor world + // ray (camera → grid hit) to check it still points at the shelf volume. + const camera = useThree((s) => s.camera) + const cameraRef = useRef(camera) + cameraRef.current = camera + // Store config callbacks in refs to avoid re-running effect when they change const configRef = useRef(config) configRef.current = config @@ -487,6 +507,50 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea let previousGridPos: [number, number, number] | null = null + // Scratch objects reused by the stickiness test (runs per grid:move). + const stickyRay = new Ray() + const stickyBox = new Box3() + const stickyMat = new Matrix4() + const stickyCamPos = new Vector3() + + // True while the cursor ray still points at the active shelf's volume. + // Used to keep an item hosted on a shelf "sticky": from an angled camera + // the cursor ray slips off the shelf's thin boards / through its gaps and + // lands on the floor *behind* the shelf, which would otherwise thrash the + // placement between the shelf row and the floor on every micro-move. We + // reconstruct the world ray (camera → grid hit point) and test it against + // the shelf's bounding box — so a ray that passes *through* the shelf but + // lands behind it still counts as "on the shelf". Only a ray that misses + // the shelf box entirely means the user genuinely moved off it. A simple + // footprint test on the floor hit point can't distinguish those. + const cursorRayIntersectsActiveShelf = (gridWorldPoint: [number, number, number]): boolean => { + const shelfId = placementState.current.shelfId + if (!shelfId) return false + const shelfMesh = sceneRegistry.nodes.get(shelfId as AnyNodeId) + const shelfNode = useScene.getState().nodes[shelfId as AnyNodeId] as + | { width?: number; depth?: number; height?: number } + | undefined + if (!(shelfMesh && shelfNode?.width && shelfNode?.depth && shelfNode?.height)) return false + + cameraRef.current.getWorldPosition(stickyCamPos) + stickyRay.origin.copy(stickyCamPos) + stickyRay.direction + .set( + gridWorldPoint[0] - stickyCamPos.x, + gridWorldPoint[1] - stickyCamPos.y, + gridWorldPoint[2] - stickyCamPos.z, + ) + .normalize() + + // Into shelf-local space, then test the shelf's local AABB (origin at the + // base: y ∈ [0, height]) with a small margin. + stickyRay.applyMatrix4(stickyMat.copy(shelfMesh.matrixWorld).invert()) + const m = 0.08 + stickyBox.min.set(-shelfNode.width / 2 - m, -m, -shelfNode.depth / 2 - m) + stickyBox.max.set(shelfNode.width / 2 + m, shelfNode.height + m, shelfNode.depth / 2 + m) + return stickyRay.intersectsBox(stickyBox) + } + const onGridMove = (event: GridEvent) => { // Lazy draft creation: if no draft yet (e.g. level wasn't ready during init), create now if (draftNode.current === null && asset.attachTo === undefined) { @@ -494,6 +558,17 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea } has3DPointerDrivenMoveRef.current = true + + // Shelf stickiness: while hosting on a shelf, ignore floor events while + // the cursor ray still points at the shelf volume (the ray merely slipped + // off a board / through a gap and hit the floor behind). Detach to the + // floor only once the ray misses the shelf entirely — without this the + // item oscillates between the shelf row and the floor on every micro-move. + if (placementState.current.surface === 'shelf-surface') { + if (cursorRayIntersectsActiveShelf(event.position)) return + detachItemSurfaceToFloor(event as unknown as ItemEvent) + } + lastRawPos.current.set(event.localPosition[0], event.localPosition[1], event.localPosition[2]) if (!cursorGroupRef.current) return const result = floorStrategy.move(getContext(), event) @@ -774,7 +849,11 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const wz = Math.round(buildingLocalPoint.z * 2) / 2 const floorPos: [number, number, number] = [wx, 0, wz] - Object.assign(placementState.current, { surface: 'floor', surfaceItemId: null }) + Object.assign(placementState.current, { + surface: 'floor', + surfaceItemId: null, + shelfId: null, + }) gridPosition.current.set(wx, 0, wz) if (cursorGroupRef.current) { cursorGroupRef.current.position.set(wx, 0, wz) @@ -1212,11 +1291,14 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const onShelfLeave = (event: ShelfEvent) => { if (placementState.current.surface !== 'shelf-surface') return if (event.node.id !== placementState.current.shelfId) return + // Intentionally do NOT detach to the floor here. `shelf:leave` fires + // constantly while hosting because the cursor ray slips off the shelf's + // thin boards and through its gaps — detaching on each of those would + // thrash the item between the shelf row and the floor. The grid handler + // owns the real shelf→floor transition (see `isOverActiveShelfFootprint` + // in `onGridMove`): it detaches only once the cursor is clearly off the + // shelf footprint, which is the genuine "left the shelf" signal. event.stopPropagation() - // Drop back to floor — same pattern as item-leave but without the - // detachItemSurfaceToFloor (no scaled rotation hand-off to deal - // with since the shelf rotation already composed cleanly). - Object.assign(placementState.current, { surface: 'floor', shelfId: null }) } const onShelfClick = (event: ShelfEvent) => { @@ -1496,9 +1578,51 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea useScene.getState().updateNode(draft.id as AnyNodeId, { parentId: viewerLevelId }) }, [viewerLevelId, draftNode, asset]) + // Disable raycasting on the live draft mesh (and restore it when the draft + // changes or goes away) so the cursor ray passes through the item being + // moved and lands on the surface beneath it. + const reconcileDraftRaycast = useCallback((mesh: Object3D | null) => { + if (raycastDisabledMeshRef.current !== mesh) { + // New draft root (or cleared): restore the prior mesh and reset tracking. + for (const restore of restoreRaycastsRef.current) restore() + restoreRaycastsRef.current = [] + raycastDisabledChildrenRef.current = new WeakSet() + raycastDisabledMeshRef.current = mesh + } + if (!mesh) return + // Disable any descendant not handled yet. Item drafts are GLB models whose + // child meshes mount asynchronously (Suspense), so a one-shot traverse + // misses them — those late children keep intercepting the ray and corrupt + // the shelf-row hit the moment the item moves onto a row. Re-walking each + // frame is cheap: the WeakSet makes it idempotent, so only new children pay. + mesh.traverse((child) => { + if (raycastDisabledChildrenRef.current.has(child)) return + raycastDisabledChildrenRef.current.add(child) + const original = child.raycast + child.raycast = () => {} + restoreRaycastsRef.current.push(() => { + child.raycast = original + }) + }) + }, []) + + // Restore the draft mesh's raycast when the coordinator unmounts (tool change). + useEffect(() => () => reconcileDraftRaycast(null), [reconcileDraftRaycast]) + useFrame((_, delta) => { - if (!asset) return - if (!draftNode.current) return + if (!asset) { + reconcileDraftRaycast(null) + return + } + if (!draftNode.current) { + reconcileDraftRaycast(null) + return + } + const mesh = sceneRegistry.nodes.get(draftNode.current.id) ?? null + reconcileDraftRaycast(mesh) + // mitt listeners outlive the cursor group's mount; bail if it's gone + // (mount/teardown race, #323). Placed after reconcileDraftRaycast so the + // draft's raycast is still restored during that window. if (!cursorGroupRef.current) return // The mesh-position lerp below only makes sense once this coordinator // owns the move via a 3D pointer event. Skip until then so that @@ -1506,7 +1630,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // writing scene.position directly) aren't fought by useFrame pulling // the mesh back to its pre-move location. if (!has3DPointerDrivenMoveRef.current) return - const mesh = sceneRegistry.nodes.get(draftNode.current.id) if (!mesh) return // Hide wall/ceiling-attached items when between surfaces (only cursor visible) diff --git a/packages/nodes/src/shelf/definition.ts b/packages/nodes/src/shelf/definition.ts index 0bc2e4699..b30967b53 100644 --- a/packages/nodes/src/shelf/definition.ts +++ b/packages/nodes/src/shelf/definition.ts @@ -185,6 +185,27 @@ export const shelfDefinition: NodeDefinition = { // system.tsx, no inline floor-plan SVG — see // `wiki/architecture/node-definitions.md`. geometry: buildShelfGeometry, + // Boards/posts/back depend only on these fields — never on hosted + // `children`. Lets skip the dispose+rebuild (and the + // pointer enter/leave churn it causes) when an item reparents onto a row. + geometryKey: (n) => { + const s = n as ShelfNodeType + return JSON.stringify([ + s.style, + s.width, + s.depth, + s.thickness, + s.height, + s.rows, + s.columns, + s.withBack, + s.withSides, + s.withBottom, + s.bracketStyle, + s.material, + s.materialPreset, + ]) + }, floorplan: buildShelfFloorplan, // 2D move handler — Path 1 in `FloorplanRegistryMoveOverlay`. Without // this the overlay falls through to Path 2 which stomps the SVG diff --git a/packages/viewer/src/systems/geometry/geometry-system.tsx b/packages/viewer/src/systems/geometry/geometry-system.tsx index 05e50652f..3e6ddaf3e 100644 --- a/packages/viewer/src/systems/geometry/geometry-system.tsx +++ b/packages/viewer/src/systems/geometry/geometry-system.tsx @@ -11,7 +11,7 @@ import { useScene, } from '@pascal-app/core' import { useFrame } from '@react-three/fiber' -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import { FrontSide, type Group, type Material, type Mesh } from 'three' import { type ColorPreset, @@ -58,6 +58,11 @@ export const GeometrySystem = () => { const textures = useViewer((s) => s.textures) const colorPreset = useViewer((s) => s.colorPreset) const sceneTheme = useViewer((s) => s.sceneTheme) + // Per-node cache of the last-built geometry key (for kinds that declare + // `def.geometryKey`). Lets us skip a dispose+rebuild when a node is dirty + // but its geometry inputs are unchanged — e.g. an item reparenting onto a + // shelf dirties the shelf without altering its boards. + const builtGeometryKeyRef = useRef>(new Map()) useEffect(() => { const nodes = useScene.getState().nodes @@ -146,6 +151,20 @@ export const GeometrySystem = () => { // now smooths drags through this single line. const effectiveNode = getEffectiveNode(node) + // Skip the rebuild when the geometry inputs are unchanged (kinds that + // opt in via `def.geometryKey`). Fold in the global rendering inputs so + // a theme / shading change — which re-dirties every geometry node — is + // never skipped. This kills the board remount + pointer enter/leave + // churn when an item reparents onto a shelf. + if (def.geometryKey) { + const builtKey = `${shading}|${textures}|${colorPreset}|${sceneTheme}|${def.geometryKey(effectiveNode)}` + if (builtGeometryKeyRef.current.get(id) === builtKey) { + clearDirty(id as AnyNodeId) + continue + } + builtGeometryKeyRef.current.set(id, builtKey) + } + const parentId = (node.parentId ?? null) as AnyNodeId | null const key: BatchKey = `${node.type}::${parentId ?? ''}` const levelData = levelDataByBatch.get(key) From 8ebfc2b29ad46d8fd66d392a123f8b1398b84bdd Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Wed, 3 Jun 2026 15:50:47 -0400 Subject: [PATCH 3/4] feat(editor-app): add Shelf to the standalone editor Build tab The shelf kind is fully wired (def.tool, presentation icon, StructureTool id) but was absent from the standalone editor's Build palette. Add it between Column and Spawn Point. Community has its own build-tab and is left untouched. Co-Authored-By: Claude Opus 4.8 --- apps/editor/components/build-tab.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/editor/components/build-tab.tsx b/apps/editor/components/build-tab.tsx index 48552e55d..175f3c8ab 100644 --- a/apps/editor/components/build-tab.tsx +++ b/apps/editor/components/build-tab.tsx @@ -26,6 +26,7 @@ type BuildToolKind = | 'door' | 'window' | 'column' + | 'shelf' | 'spawn' type BuildType = { @@ -51,6 +52,7 @@ const BUILD_TYPES: BuildType[] = [ { id: 'door', label: 'Door', iconSrc: '/icons/door.png', kind: 'door' }, { id: 'window', label: 'Window', iconSrc: '/icons/window.png', kind: 'window' }, { id: 'column', label: 'Column', iconSrc: '/icons/column.png', kind: 'column' }, + { id: 'shelf', label: 'Shelf', iconSrc: '/icons/shelf.png', kind: 'shelf' }, { id: 'spawn', label: 'Spawn Point', iconSrc: '/icons/site.png', kind: 'spawn' }, { id: 'painting', label: 'Painting', iconSrc: '/icons/paint.png', mode: 'material-paint' }, ] From 2431966671729a5b0c4fb117090f194d28b9c6bf Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Wed, 3 Jun 2026 16:04:14 -0400 Subject: [PATCH 4/4] chore(mcp): biome-format coordinate-conventions-demo.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The committed example violated Biome formatting (expanded polygon arrays), which broke the `mcp-ci` Biome check on main (#356) and every branch since. Format it so CI is green. Pure formatting — no content change. Co-Authored-By: Claude Opus 4.8 --- .../examples/coordinate-conventions-demo.json | 1372 ++--------------- 1 file changed, 108 insertions(+), 1264 deletions(-) diff --git a/packages/mcp/examples/coordinate-conventions-demo.json b/packages/mcp/examples/coordinate-conventions-demo.json index e4026897a..af2de3a9a 100644 --- a/packages/mcp/examples/coordinate-conventions-demo.json +++ b/packages/mcp/examples/coordinate-conventions-demo.json @@ -6,24 +6,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 18, - 0 - ], - [ - 24, - 0 - ], - [ - 24, - 4 - ], - [ - 18, - 4 - ] - ], + "polygon": [[18, 0], [24, 0], [24, 4], [18, 4]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -37,24 +20,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 18, - 10 - ], - [ - 23.195999999999998, - 13 - ], - [ - 21.196, - 16.464 - ], - [ - 16, - 13.464 - ] - ], + "polygon": [[18, 10], [23.195999999999998, 13], [21.196, 16.464], [16, 13.464]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -68,24 +34,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -0.3, - -0.3 - ], - [ - 0.3, - -0.3 - ], - [ - 0.3, - 0.3 - ], - [ - -0.3, - 0.3 - ] - ], + "polygon": [[-0.3, -0.3], [0.3, -0.3], [0.3, 0.3], [-0.3, 0.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -100,29 +49,10 @@ "object": "node", "polygon": { "type": "polygon", - "points": [ - [ - -6, - -12 - ], - [ - 44, - -12 - ], - [ - 44, - 40 - ], - [ - -6, - 40 - ] - ] + "points": [[-6, -12], [44, -12], [44, 40], [-6, 40]] }, "visible": true, - "children": [ - "building_conv_demo" - ], + "children": ["building_conv_demo"], "metadata": {}, "parentId": null }, @@ -214,24 +144,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -2.5, - -0.06 - ], - [ - 0, - -0.06 - ], - [ - 0, - 0.06 - ], - [ - -2.5, - 0.06 - ] - ], + "polygon": [[-2.5, -0.06], [0, -0.06], [0, 0.06], [-2.5, 0.06]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -245,24 +158,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -0.06, - -2.5 - ], - [ - 0.06, - -2.5 - ], - [ - 0.06, - 0 - ], - [ - -0.06, - 0 - ] - ], + "polygon": [[-0.06, -2.5], [0.06, -2.5], [0.06, 0], [-0.06, 0]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -276,24 +172,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 0, - -0.06 - ], - [ - 12, - -0.06 - ], - [ - 12, - 0.06 - ], - [ - 0, - 0.06 - ] - ], + "polygon": [[0, -0.06], [12, -0.06], [12, 0.06], [0, 0.06]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -307,20 +186,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 12, - -0.4 - ], - [ - 12.9, - 0 - ], - [ - 12, - 0.4 - ] - ], + "polygon": [[12, -0.4], [12.9, 0], [12, 0.4]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -334,24 +200,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -0.06, - 0 - ], - [ - 0.06, - 0 - ], - [ - 0.06, - 12 - ], - [ - -0.06, - 12 - ] - ], + "polygon": [[-0.06, 0], [0.06, 0], [0.06, 12], [-0.06, 12]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -365,20 +214,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -0.4, - 12 - ], - [ - 0.4, - 12 - ], - [ - 0, - 12.9 - ] - ], + "polygon": [[-0.4, 12], [0.4, 12], [0, 12.9]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -392,24 +228,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 20.7, - 1.3 - ], - [ - 21.3, - 1.3 - ], - [ - 21.3, - 1.9000000000000001 - ], - [ - 20.7, - 1.9000000000000001 - ] - ], + "polygon": [[20.7, 1.3], [21.3, 1.3], [21.3, 1.9000000000000001], [20.7, 1.9000000000000001]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -421,22 +240,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 13.299999999999999, - -1.3 - ], - [ - 13.9, - -1.3 - ], - [ - 13.9, - -0.7 - ], - [ - 13.299999999999999, - -0.7 - ] + [13.299999999999999, -1.3], + [13.9, -1.3], + [13.9, -0.7], + [13.299999999999999, -0.7] ], "visible": true, "metadata": {}, @@ -449,22 +256,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - -1.3, - 13.299999999999999 - ], - [ - -0.7, - 13.299999999999999 - ], - [ - -0.7, - 13.9 - ], - [ - -1.3, - 13.9 - ] + [-1.3, 13.299999999999999], + [-0.7, 13.299999999999999], + [-0.7, 13.9], + [-1.3, 13.9] ], "visible": true, "metadata": {}, @@ -476,24 +271,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 1.2, - 1.2 - ], - [ - 1.8, - 1.2 - ], - [ - 1.8, - 1.8 - ], - [ - 1.2, - 1.8 - ] - ], + "polygon": [[1.2, 1.2], [1.8, 1.2], [1.8, 1.8], [1.2, 1.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -504,24 +282,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 2.7, - 32.7 - ], - [ - 3.3, - 32.7 - ], - [ - 3.3, - 33.3 - ], - [ - 2.7, - 33.3 - ] - ], + "polygon": [[2.7, 32.7], [3.3, 32.7], [3.3, 33.3], [2.7, 33.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -532,24 +293,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 2.7, - 33.5 - ], - [ - 3.3, - 33.5 - ], - [ - 3.3, - 34.099999999999994 - ], - [ - 2.7, - 34.099999999999994 - ] - ], + "polygon": [[2.7, 33.5], [3.3, 33.5], [3.3, 34.099999999999994], [2.7, 34.099999999999994]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -560,24 +304,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 2.7, - 34.300000000000004 - ], - [ - 3.3, - 34.300000000000004 - ], - [ - 3.3, - 34.9 - ], - [ - 2.7, - 34.9 - ] - ], + "polygon": [[2.7, 34.300000000000004], [3.3, 34.300000000000004], [3.3, 34.9], [2.7, 34.9]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -588,24 +315,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 2.7, - 35.1 - ], - [ - 3.3, - 35.1 - ], - [ - 3.3, - 35.699999999999996 - ], - [ - 2.7, - 35.699999999999996 - ] - ], + "polygon": [[2.7, 35.1], [3.3, 35.1], [3.3, 35.699999999999996], [2.7, 35.699999999999996]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -616,24 +326,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 21.7, - 32.7 - ], - [ - 22.3, - 32.7 - ], - [ - 22.3, - 33.3 - ], - [ - 21.7, - 33.3 - ] - ], + "polygon": [[21.7, 32.7], [22.3, 32.7], [22.3, 33.3], [21.7, 33.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -644,32 +337,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 0, - 22 - ], - [ - 2, - 22 - ], - [ - 2, - 22.5 - ], - [ - 0.5, - 22.5 - ], - [ - 0.5, - 24.5 - ], - [ - 0, - 24.5 - ] - ], + "polygon": [[0, 22], [2, 22], [2, 22.5], [0.5, 22.5], [0.5, 24.5], [0, 24.5]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -683,32 +351,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 22, - 24.5 - ], - [ - 24, - 24.5 - ], - [ - 24, - 24 - ], - [ - 22.5, - 24 - ], - [ - 22.5, - 22 - ], - [ - 22, - 22 - ] - ], + "polygon": [[22, 24.5], [24, 24.5], [24, 24], [22.5, 24], [22.5, 22], [22, 22]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -722,24 +365,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 24.5, - 1.7 - ], - [ - 25.1, - 1.7 - ], - [ - 25.1, - 2.3 - ], - [ - 24.5, - 2.3 - ] - ], + "polygon": [[24.5, 1.7], [25.1, 1.7], [25.1, 2.3], [24.5, 2.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -750,24 +376,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 20.7, - -1.3 - ], - [ - 21.3, - -1.3 - ], - [ - 21.3, - -0.7 - ], - [ - 20.7, - -0.7 - ] - ], + "polygon": [[20.7, -1.3], [21.3, -1.3], [21.3, -0.7], [20.7, -0.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -778,24 +387,7 @@ "type": "zone", "color": "#ffe9c2", "object": "node", - "polygon": [ - [ - 6.2, - 24.2 - ], - [ - 6.8, - 24.2 - ], - [ - 6.8, - 24.8 - ], - [ - 6.2, - 24.8 - ] - ], + "polygon": [[6.2, 24.2], [6.8, 24.2], [6.8, 24.8], [6.2, 24.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -806,24 +398,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 2.1, - 21.5 - ], - [ - 2.6999999999999997, - 21.5 - ], - [ - 2.6999999999999997, - 22.1 - ], - [ - 2.1, - 22.1 - ] - ], + "polygon": [[2.1, 21.5], [2.6999999999999997, 21.5], [2.6999999999999997, 22.1], [2.1, 22.1]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -835,22 +410,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - -0.7, - 24.099999999999998 - ], - [ - -0.10000000000000003, - 24.099999999999998 - ], - [ - -0.10000000000000003, - 24.7 - ], - [ - -0.7, - 24.7 - ] + [-0.7, 24.099999999999998], + [-0.10000000000000003, 24.099999999999998], + [-0.10000000000000003, 24.7], + [-0.7, 24.7] ], "visible": true, "metadata": {}, @@ -862,24 +425,7 @@ "type": "zone", "color": "#ffe9c2", "object": "node", - "polygon": [ - [ - 28.2, - 24.2 - ], - [ - 28.8, - 24.2 - ], - [ - 28.8, - 24.8 - ], - [ - 28.2, - 24.8 - ] - ], + "polygon": [[28.2, 24.2], [28.8, 24.2], [28.8, 24.8], [28.2, 24.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -891,22 +437,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 24.099999999999998, - 21.5 - ], - [ - 24.7, - 21.5 - ], - [ - 24.7, - 22.1 - ], - [ - 24.099999999999998, - 22.1 - ] + [24.099999999999998, 21.5], + [24.7, 21.5], + [24.7, 22.1], + [24.099999999999998, 22.1] ], "visible": true, "metadata": {}, @@ -919,22 +453,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 21.3, - 24.099999999999998 - ], - [ - 21.900000000000002, - 24.099999999999998 - ], - [ - 21.900000000000002, - 24.7 - ], - [ - 21.3, - 24.7 - ] + [21.3, 24.099999999999998], + [21.900000000000002, 24.099999999999998], + [21.900000000000002, 24.7], + [21.3, 24.7] ], "visible": true, "metadata": {}, @@ -946,32 +468,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 10, - 22 - ], - [ - 14, - 22 - ], - [ - 14, - 23 - ], - [ - 11, - 23 - ], - [ - 11, - 27 - ], - [ - 10, - 27 - ] - ], + "polygon": [[10, 22], [14, 22], [14, 23], [11, 23], [11, 27], [10, 27]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -985,32 +482,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 32, - 27 - ], - [ - 36, - 27 - ], - [ - 36, - 26 - ], - [ - 33, - 26 - ], - [ - 33, - 22 - ], - [ - 32, - 22 - ] - ], + "polygon": [[32, 27], [36, 27], [36, 26], [33, 26], [33, 22], [32, 22]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -1024,24 +496,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 18.2, - 0.2 - ], - [ - 18.8, - 0.2 - ], - [ - 18.8, - 0.8 - ], - [ - 18.2, - 0.8 - ] - ], + "polygon": [[18.2, 0.2], [18.8, 0.2], [18.8, 0.8], [18.2, 0.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1052,24 +507,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 18.2, - 10.2 - ], - [ - 18.8, - 10.2 - ], - [ - 18.8, - 10.8 - ], - [ - 18.2, - 10.8 - ] - ], + "polygon": [[18.2, 10.2], [18.8, 10.2], [18.8, 10.8], [18.2, 10.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1080,24 +518,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 1.2, - 21.2 - ], - [ - 1.8, - 21.2 - ], - [ - 1.8, - 21.8 - ], - [ - 1.2, - 21.8 - ] - ], + "polygon": [[1.2, 21.2], [1.8, 21.2], [1.8, 21.8], [1.2, 21.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1109,22 +530,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - -1.1, - 23.099999999999998 - ], - [ - -0.5, - 23.099999999999998 - ], - [ - -0.5, - 23.7 - ], - [ - -1.1, - 23.7 - ] + [-1.1, 23.099999999999998], + [-0.5, 23.099999999999998], + [-0.5, 23.7], + [-1.1, 23.7] ], "visible": true, "metadata": {}, @@ -1136,24 +545,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 12.2, - 22.2 - ], - [ - 12.8, - 22.2 - ], - [ - 12.8, - 22.8 - ], - [ - 12.2, - 22.8 - ] - ], + "polygon": [[12.2, 22.2], [12.8, 22.2], [12.8, 22.8], [12.2, 22.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1164,24 +556,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 10.2, - 24.2 - ], - [ - 10.8, - 24.2 - ], - [ - 10.8, - 24.8 - ], - [ - 10.2, - 24.8 - ] - ], + "polygon": [[10.2, 24.2], [10.8, 24.2], [10.8, 24.8], [10.2, 24.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1192,24 +567,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -6.3 - ], - [ - 5.3, - -6.3 - ], - [ - 5.3, - -5.7 - ], - [ - 4.7, - -5.7 - ] - ], + "polygon": [[4.7, -6.3], [5.3, -6.3], [5.3, -5.7], [4.7, -5.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1220,24 +578,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -7.3 - ], - [ - 5.3, - -7.3 - ], - [ - 5.3, - -6.7 - ], - [ - 4.7, - -6.7 - ] - ], + "polygon": [[4.7, -7.3], [5.3, -7.3], [5.3, -6.7], [4.7, -6.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1248,24 +589,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -8.3 - ], - [ - 5.3, - -8.3 - ], - [ - 5.3, - -7.7 - ], - [ - 4.7, - -7.7 - ] - ], + "polygon": [[4.7, -8.3], [5.3, -8.3], [5.3, -7.7], [4.7, -7.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1276,24 +600,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -9.3 - ], - [ - 5.3, - -9.3 - ], - [ - 5.3, - -8.7 - ], - [ - 4.7, - -8.7 - ] - ], + "polygon": [[4.7, -9.3], [5.3, -9.3], [5.3, -8.7], [4.7, -8.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1304,24 +611,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 22.7, - 25.2 - ], - [ - 23.3, - 25.2 - ], - [ - 23.3, - 25.8 - ], - [ - 22.7, - 25.8 - ] - ], + "polygon": [[22.7, 25.2], [23.3, 25.2], [23.3, 25.8], [22.7, 25.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1332,21 +622,11 @@ "type": "building", "object": "node", "visible": true, - "children": [ - "level_conv_demo" - ], + "children": ["level_conv_demo"], "metadata": {}, "parentId": "site_conv_demo", - "position": [ - 0, - 0, - 0 - ], - "rotation": [ - 0, - 0, - 0 - ] + "position": [0, 0, 0], + "rotation": [0, 0, 0] }, "zone_lbl_b_title_1": { "id": "zone_lbl_b_title_1", @@ -1354,24 +634,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 21.7, - 8.2 - ], - [ - 22.3, - 8.2 - ], - [ - 22.3, - 8.8 - ], - [ - 21.7, - 8.8 - ] - ], + "polygon": [[21.7, 8.2], [22.3, 8.2], [22.3, 8.8], [21.7, 8.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1382,24 +645,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 21.7, - 9 - ], - [ - 22.3, - 9 - ], - [ - 22.3, - 9.600000000000001 - ], - [ - 21.7, - 9.600000000000001 - ] - ], + "polygon": [[21.7, 9], [22.3, 9], [22.3, 9.600000000000001], [21.7, 9.600000000000001]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1410,24 +656,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 0.7, - 20.7 - ], - [ - 1.3, - 20.7 - ], - [ - 1.3, - 21.3 - ], - [ - 0.7, - 21.3 - ] - ], + "polygon": [[0.7, 20.7], [1.3, 20.7], [1.3, 21.3], [0.7, 21.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1438,24 +667,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 11.7, - 20.7 - ], - [ - 12.3, - 20.7 - ], - [ - 12.3, - 21.3 - ], - [ - 11.7, - 21.3 - ] - ], + "polygon": [[11.7, 20.7], [12.3, 20.7], [12.3, 21.3], [11.7, 21.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1466,24 +678,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -3.8 - ], - [ - 5.3, - -3.8 - ], - [ - 5.3, - -3.2 - ], - [ - 4.7, - -3.2 - ] - ], + "polygon": [[4.7, -3.8], [5.3, -3.8], [5.3, -3.2], [4.7, -3.2]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1494,24 +689,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 4.7, - -4.8 - ], - [ - 5.3, - -4.8 - ], - [ - 5.3, - -4.2 - ], - [ - 4.7, - -4.2 - ] - ], + "polygon": [[4.7, -4.8], [5.3, -4.8], [5.3, -4.2], [4.7, -4.2]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1522,24 +700,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 33.7, - 20.7 - ], - [ - 34.3, - 20.7 - ], - [ - 34.3, - 21.3 - ], - [ - 33.7, - 21.3 - ] - ], + "polygon": [[33.7, 20.7], [34.3, 20.7], [34.3, 21.3], [33.7, 21.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1550,24 +711,7 @@ "type": "zone", "color": "#fff5d6", "object": "node", - "polygon": [ - [ - 0, - 32 - ], - [ - 40, - 32 - ], - [ - 40, - 36 - ], - [ - 0, - 36 - ] - ], + "polygon": [[0, 32], [40, 32], [40, 36], [0, 36]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1578,24 +722,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 20.7, - 5.2 - ], - [ - 21.3, - 5.2 - ], - [ - 21.3, - 5.8 - ], - [ - 20.7, - 5.8 - ] - ], + "polygon": [[20.7, 5.2], [21.3, 5.2], [21.3, 5.8], [20.7, 5.8]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1606,24 +733,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - -2.3, - 0.7 - ], - [ - -1.7, - 0.7 - ], - [ - -1.7, - 1.3 - ], - [ - -2.3, - 1.3 - ] - ], + "polygon": [[-2.3, 0.7], [-1.7, 0.7], [-1.7, 1.3], [-2.3, 1.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1634,24 +744,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 0.7, - -2.3 - ], - [ - 1.3, - -2.3 - ], - [ - 1.3, - -1.7 - ], - [ - 0.7, - -1.7 - ] - ], + "polygon": [[0.7, -2.3], [1.3, -2.3], [1.3, -1.7], [0.7, -1.7]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1663,22 +756,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 33.7, - 27.3 - ], - [ - 34.3, - 27.3 - ], - [ - 34.3, - 27.900000000000002 - ], - [ - 33.7, - 27.900000000000002 - ] + [33.7, 27.3], + [34.3, 27.3], + [34.3, 27.900000000000002], + [33.7, 27.900000000000002] ], "visible": true, "metadata": {}, @@ -1691,22 +772,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 33.7, - 28.099999999999998 - ], - [ - 34.3, - 28.099999999999998 - ], - [ - 34.3, - 28.7 - ], - [ - 33.7, - 28.7 - ] + [33.7, 28.099999999999998], + [34.3, 28.099999999999998], + [34.3, 28.7], + [33.7, 28.7] ], "visible": true, "metadata": {}, @@ -1718,24 +787,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 33.7, - 28.9 - ], - [ - 34.3, - 28.9 - ], - [ - 34.3, - 29.5 - ], - [ - 33.7, - 29.5 - ] - ], + "polygon": [[33.7, 28.9], [34.3, 28.9], [34.3, 29.5], [33.7, 29.5]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1746,24 +798,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 33.7, - 29.7 - ], - [ - 34.3, - 29.7 - ], - [ - 34.3, - 30.3 - ], - [ - 33.7, - 30.3 - ] - ], + "polygon": [[33.7, 29.7], [34.3, 29.7], [34.3, 30.3], [33.7, 30.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1774,24 +809,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 33.7, - 30.5 - ], - [ - 34.3, - 30.5 - ], - [ - 34.3, - 31.1 - ], - [ - 33.7, - 31.1 - ] - ], + "polygon": [[33.7, 30.5], [34.3, 30.5], [34.3, 31.1], [33.7, 31.1]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1802,24 +820,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 22.7, - 20.7 - ], - [ - 23.3, - 20.7 - ], - [ - 23.3, - 21.3 - ], - [ - 22.7, - 21.3 - ] - ], + "polygon": [[22.7, 20.7], [23.3, 20.7], [23.3, 21.3], [22.7, 21.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1830,24 +831,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 22.7, - 19.9 - ], - [ - 23.3, - 19.9 - ], - [ - 23.3, - 20.5 - ], - [ - 22.7, - 20.5 - ] - ], + "polygon": [[22.7, 19.9], [23.3, 19.9], [23.3, 20.5], [22.7, 20.5]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -1858,24 +842,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 0.1, - 21.95 - ], - [ - 1.9, - 21.95 - ], - [ - 1.9, - 22.05 - ], - [ - 0.1, - 22.05 - ] - ], + "polygon": [[0.1, 21.95], [1.9, 21.95], [1.9, 22.05], [0.1, 22.05]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -1889,24 +856,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - -0.05, - 22.1 - ], - [ - 0.05, - 22.1 - ], - [ - 0.05, - 23.9 - ], - [ - -0.05, - 23.9 - ] - ], + "polygon": [[-0.05, 22.1], [0.05, 22.1], [0.05, 23.9], [-0.05, 23.9]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -1920,24 +870,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 22.1, - 21.95 - ], - [ - 23.9, - 21.95 - ], - [ - 23.9, - 22.05 - ], - [ - 22.1, - 22.05 - ] - ], + "polygon": [[22.1, 21.95], [23.9, 21.95], [23.9, 22.05], [22.1, 22.05]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -1951,24 +884,7 @@ "type": "slab", "holes": [], "object": "node", - "polygon": [ - [ - 21.95, - 22.1 - ], - [ - 22.05, - 22.1 - ], - [ - 22.05, - 23.9 - ], - [ - 21.95, - 23.9 - ] - ], + "polygon": [[21.95, 22.1], [22.05, 22.1], [22.05, 23.9], [21.95, 23.9]], "visible": true, "metadata": {}, "parentId": "level_conv_demo", @@ -1982,24 +898,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 21.7, - 16.7 - ], - [ - 22.3, - 16.7 - ], - [ - 22.3, - 17.3 - ], - [ - 21.7, - 17.3 - ] - ], + "polygon": [[21.7, 16.7], [22.3, 16.7], [22.3, 17.3], [21.7, 17.3]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -2010,24 +909,7 @@ "type": "zone", "color": "#f0f0f0", "object": "node", - "polygon": [ - [ - 21.7, - 17.5 - ], - [ - 22.3, - 17.5 - ], - [ - 22.3, - 18.1 - ], - [ - 21.7, - 18.1 - ] - ], + "polygon": [[21.7, 17.5], [22.3, 17.5], [22.3, 18.1], [21.7, 18.1]], "visible": true, "metadata": {}, "parentId": "level_conv_demo" @@ -2039,22 +921,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 21.7, - 18.3 - ], - [ - 22.3, - 18.3 - ], - [ - 22.3, - 18.900000000000002 - ], - [ - 21.7, - 18.900000000000002 - ] + [21.7, 18.3], + [22.3, 18.3], + [22.3, 18.900000000000002], + [21.7, 18.900000000000002] ], "visible": true, "metadata": {}, @@ -2067,22 +937,10 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 11.7, - 27.3 - ], - [ - 12.3, - 27.3 - ], - [ - 12.3, - 27.900000000000002 - ], - [ - 11.7, - 27.900000000000002 - ] + [11.7, 27.3], + [12.3, 27.3], + [12.3, 27.900000000000002], + [11.7, 27.900000000000002] ], "visible": true, "metadata": {}, @@ -2095,30 +953,16 @@ "color": "#f0f0f0", "object": "node", "polygon": [ - [ - 11.7, - 28.099999999999998 - ], - [ - 12.3, - 28.099999999999998 - ], - [ - 12.3, - 28.7 - ], - [ - 11.7, - 28.7 - ] + [11.7, 28.099999999999998], + [12.3, 28.099999999999998], + [12.3, 28.7], + [11.7, 28.7] ], "visible": true, "metadata": {}, "parentId": "level_conv_demo" } }, - "rootNodeIds": [ - "site_conv_demo" - ], + "rootNodeIds": ["site_conv_demo"], "collections": {} -} \ No newline at end of file +}