e484e17f82dd97532df23e8a5a5c92a836153d71
1198
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e484e17f82 |
fix(viewer): kick render on resume (#516)
Co-authored-by: mvanhorn <mvanhorn@users.noreply.github.com> |
||
|
|
f67787077e |
fix(core): guard material-ref parsers against non-string values (Sentry MONOREPO-EDITOR-EM) (#486)
* fix(core): guard material-ref parsers against non-string values (Sentry MONOREPO-EDITOR-EM) getLibraryMaterialIdFromRef and getSceneMaterialIdFromRef only guarded against null/undefined, then called .startsWith(). When a non-string material ref reaches them (legacy/malformed wall material slot ref), .startsWith is undefined -> TypeError: e.startsWith is not a function. Narrow with typeof !== 'string' -> return null, so a bad ref degrades to 'no library/scene material' instead of throwing during wall material resolution (packages/viewer wall-materials.ts -> parseMaterialRef). * test(core): cover malformed material refs --------- Co-authored-by: Aymeric Rabot <aymeric.rabot@gmail.com> |
||
|
|
5c7f58f295 |
Merge pull request #515 from pascalorg/fix/preload-tool-inspector
Preload the selected tool inspector |
||
|
|
16ea186f77 |
fix(viewer): guard texture.repeat against non-array values (Sentry MONOREPO-EDITOR-EK) (#504)
* fix(viewer): guard texture.repeat against non-array values (Sentry MONOREPO-EDITOR-EK) * fix(viewer): normalize legacy texture repeats --------- Co-authored-by: openclaw-agent <agent@pascal.app> Co-authored-by: Aymeric Rabot <aymeric.rabot@gmail.com> |
||
|
|
d0ac9fb739 |
fix(editor): guard GLB export against undefined array-material slots (Sentry MONOREPO-EDITOR-79) (#459)
Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com> |
||
|
|
2129eed0c2 | fix(editor): preload selected tool inspector | ||
|
|
6cc10c929e |
editor: improve cabinet resizing and wall alignment (#503)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * fix wall treatment miter geometry * fix cabinet group resizing and corner alignment * fix cabinet corner depth resizing * fix(cabinet): stabilize modular preset changes * fix(cabinet): stabilize corner resizing and wall alignment * fix(nodes): stabilize wall cabinet depth resizing * fix(editor): hide cabinet arrows for module selection * feat(cabinet): add individual width resize handles * feat(cabinet): improve wall cabinet editing * fix(cabinet): harden wall cabinet resizing * fix(cabinet): correct wall drag and depth handles * fix(cabinet): refine individual depth resizing * fix(cabinet): preserve context-aware corner depth behavior * fix(editor): respect snapping modes for resize handles * fix(editor): harden cabinet resize interactions --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Aymeric Rabot <aymeric.rabot@gmail.com> |
||
|
|
c0a5db935e |
Merge pull request #514 from pascalorg/feat/host-integration-primitives
Expose generic host integration primitives |
||
|
|
8f1c834e56 | feat(core): expose generic host integration primitives | ||
|
|
2084892383 | fix: derive zone quantities from room topology (#512) | ||
|
|
cddb73b232 | fix: harden measurement geometry (#507) | ||
|
|
99cc537e25 | release: @pascal-app/core@0.9.2 @pascal-app/viewer@0.9.2 @pascal-app/editor@0.9.2 @pascal-app/nodes@0.1.1 @pascal-app/mcp@0.3.2 @pascal-app/ifc-converter@0.1.2 | ||
|
|
cdf026bb92 | fix: publish built-in nodes package (#506) | ||
|
|
ae87ca5475 |
feat: add production measurement tools (#505)
* feat: add persistent measurement tools * feat: make measurements associative * fix: finish measurements with Escape * feat: improve measurement snapping guides * feat: clarify measurement axis feedback * feat: smart measure lens, zone reports, and direct measurement editing - Smart measurement lens: registry-owned wall/slab/zone hover reports with a single top-center HUD, click-to-pin, latest-event back pressure, and no scene writes - Conservative derived zone quantities (footprint, perimeter, proven enclosure, gross wall/floor surface, flat-room volume) with the selected-zone blueprint panel - Direct editing of committed measurements via selected-only 2D/3D vertex affordances with midpoint insertion, cancellation, and one-write history - Shared measurement surface-query session; 2D tracing joins the slab/ceiling magnetic pipeline with registered-corner snapping - Angle arcs on the smaller angle, indigo active/black resting hierarchy, screen-sized normal-aligned contact rings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make measurement snapping always magnetic Measurement drafting and committed-edit paths gated wall, semantic, and axis magnetism on isMagneticSnapActive(), which is only true in the 'lines' snapping mode — in the default 'grid' mode corners and wall intersections barely attracted (3D association fell to the 0.012 m verify tolerance, 2D wall radii to the 0.05 m connect stick). Measurement is an analysis tool whose anchors exist to bind real geometry, so its snapping no longer consults the construction snapping-mode chip: 2D/3D drafting and committed vertex edits are always magnetic, Alt is the temporary bypass in both views (releasing the axis pull, wall magnetism, and the 2D projected-geometry pull, and shrinking association to contact tolerance). A discrete 2D wall snap (endpoint / midpoint / crossing) now outranks the locked axis pull, and committed 2D edits route the fallback through the raw pointer so free drags no longer quantize to the construction grid. Volume extrusion height keeps its mode-driven grid quantize. Codex adversarial review confirmed the diagnosis and plumbing; its 2D Alt-depth and grid-quantize findings are applied. New surface-plan-snap tests pin the magnetic override seam. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(measurement): stabilize area surface intent --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
22c9472066 |
Adjustments pass: shadow bias, polygon editor UX, space-detection load fix (#502)
* fix(viewer): tune shadow biases to stop acne without detaching shadows normalBias 0.02 was too small a texel offset for the building-fit 1024 shadow map and brought back self-shadowing acne. Settle on normalBias 0.08 (0.07 and below acnes, 0.1 reads detached) plus depth bias -0.0005 to suppress the residual acne that a normal bias alone couldn't clear. Also adds a `?debug=shadowcamera` diagnostic that draws a CameraHelper for each shadow camera so the fitted frustum can be inspected while tuning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): polygon editor handle UX — focus, cursors, visibility - vertex/midpoint cylinders ignore scene depth like the edge arrows so they stay visible through walls and slabs; vertex radius 0.1 -> 0.08, midpoint 0.06 -> 0.05, midpoints use the brighter arrow shade at rest - during any drag only the active handle stays mounted (other arrows, vertices, edge bars and the cross disappear) so the gesture reads clearly - handles set pointer cursors: move for vertices/midpoints/cross, and a screen-space direction-aware resize cursor for the edge arrows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): keep ceiling affordances quiet during any interaction The ceiling corner brackets' hit boxes caught drag-time hover (spatial events keep firing during host drags by design), set hoveredId to the ceiling and flashed the ceiling grid mid-gesture — e.g. while dragging a slab polygon vertex. Unmount the brackets while ANY interaction scope is active, and gate CeilingSystem's hover-driven grid reveal on idle scope + no inputDragging as a second layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): stop room detection resurrecting deleted slabs on load Two holes in the wall-driven auto slab sync: - initSpaceDetectionSync started with an empty baseline, so scene hydration (one atomic setScene) read as 'every wall changed' and ran a full detection pass on every load, recreating auto slabs the user had deleted. Seed the baseline from the store at init and treat a level's first snapshot as baseline — detection now only reacts to in-session wall edits. - matchesManualFootprint required mutual coverage, so a single manual slab spanning multiple rooms never suppressed those rooms' auto slabs (only a fraction of it lies inside each room). Suppression now only asks whether the ROOM is substantially covered by the union of manual slabs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ca3eaa7fe | feat: add project plugin management (#501) | ||
|
|
4f0aa7f7b6 |
fix(core): prevent unbounded slab corner miters (#500)
* fix(core): bound slab corner miters * style(core): format slab miter fix |
||
|
|
78223d61cf | fix(core): exclude dangling walls from room polygons (#499) | ||
|
|
a524da1574 |
Fix room surfaces, wall openings, and paint scope (#498)
* chore(core): point material catalog at KTX2 tiers for wood/flooring/roofing finishes All 48 remaining webp/jpg/png finish entries now reference _512.ktx2 maps and 256px _thumb.webp previews, matching the fabric/leather/concrete/metal convention. flipY set to false on the converted entries — compressed textures can't be flipped at upload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: stabilize room surfaces and wall openings * style(core): format KTX2 material catalog --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4fca38a3ef |
fix: make room walls and slabs join cleanly (#497)
* fix(editor): wall endpoint move — detach/attach modifier sync and zero-move drop - restore linked walls to original positions the moment alt-detach engages - re-run the endpoint preview on alt keydown/keyup so re-attach doesn't wait for a mousemove; preview, HUD badge, and commit share one alt source - second click at an unmoved position cleanly drops the endpoint (no history entry) instead of leaving the interaction stuck - 2D floorplan parity: clear stale linked overrides on detach, re-apply drag sessions on modifier changes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): demote orphaned auto slabs/ceilings to manual instead of deleting Deleting a wall that encloses a room no longer destroys the room's slab/ceiling (paint, holes, elevation). Unmatched auto surfaces are only deleted when >=60% of their footprint is still covered by a detected room (rooms-merged case); otherwise they are demoted to manual nodes. - demoted slab polygons are baked (inset by SLAB_OUTSET + AUTO_SLAB_INSET) so the rendered footprint doesn't jump between the auto and manual paths - auto-creation suppression now also matches manual nodes by mutual footprint coverage, so re-closing the room doesn't stack a duplicate auto surface on the demoted one Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): hide alt-to-detach hint when the moving endpoint has no linked walls Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): split host walls in every snap mode, stop wall chain on existing walls Splitting a room with a wall now splits the walls it lands on and migrates hosted doors/windows/items — matching what already happened to the slab and ceiling. The split machinery existed but was gated on magnetic snapping ('lines' mode) while the wall tool defaults to 'grid'; split resolution now always runs, with the join radius scoped to the active mode (0.35 magnetic, 0.05 connect snap otherwise), and the whole commit lands as one undo step. The drawing chain now terminates when a committed segment ends on a wall outside the current chain (T-junction), like the room auto-close — users don't draw overlapping walls. Applied in 3D and 2D, and the 2D-only path gains the previously missing wallClosesRoom parity check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): per-edge build-time slab offsets — slabs tile at wall centerlines Replaces the stored-polygon render offsets (auto: centroid inset 0.02, manual: flat outset 0.05) with one uniform per-edge rule computed at geometry build time, for auto and manual slabs alike: - edge shared with a sibling slab (collinear-overlap test, T-junction sub-segments included) → small relief inset; adjacent rooms tile exactly and can never overlap - edge on a wall centerline with no slab neighbor → expand outward by that wall's thickness/2, flush with the facade - free edge → rendered exactly as drawn Slab demotion no longer bakes polygons (offsets never live in node data); no stored-data migration. New slab system marks level slabs dirty when wall geometry/thickness or sibling slab footprints change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): import KTX2Loader from its deep path, not the jsm aggregate The Addons.js aggregate re-exports LottieLoader/TTFLoader whose CDN URL imports (lottie-web, opentype.js) abort bun test in every package that transitively imports the viewer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): split host wall when an endpoint move lands on its interior Moving a wall endpoint onto another wall's mid-span now splits that wall and migrates its doors/windows/items, matching the draw tool — previously the room closed (detection planarizes internally) but the wall node stayed whole. New resolveEndpointWallSplit reuses the draw path's split pipeline; endpoint write + split commit as one undo step. Applied in 3D and 2D. Corner drops still join without splitting; straddling openings skip the split; alt-detached commits split the stationary former sibling correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): slab-wall adoption — absolute edge projection, band snapping, rendered-footprint wall elevation Slab edges near a wall now render projected onto the wall's exact geometry instead of getting relative offsets, healing legacy scenes (face-aligned edges, old baked outsets, hand-drag slop) with no data migration: - adoption band: an edge collinear with a wall centerline within thickness/2 + 6cm (both sides) is wall-backed; nearest centerline wins, a sibling slab in the same band forces the interior seam - wall-backed exterior edges project to the outer face; interior edges to the centerline minus the relief gap; free edges render as drawn - slab edge resize: wall snapping is now edge-based, not cursor-based — fixes the drop landing short by the grab offset (0.34m arrow gap in 3D, hit-stroke slop in 2D); the snap translates the edge onto the wall centerline (canonical stored position), beacon/preview/commit agree, full band in lines mode, 5cm stick otherwise; 2D shows a dashed stored-boundary skeleton when it diverges from the fill - wall elevation tests the slab's rendered footprint instead of the stored polygon with a 0.1mm epsilon, so walls sit on legacy slabs and re-elevate when slabs are reshaped Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): subcut slab edges per backing span A slab edge backed differently along its length (offset rooms sharing a wall over part of the span, a wall shorter than the edge, collinear walls of different thickness) is now subdivided at the backing-span breakpoints; each sub-span classifies and projects independently, with a perpendicular step connector at intra-edge transitions that lands inside the crossing wall's footprint. Breakpoints closer than 5cm merge so no sliver geometry reaches the ring, and same-target spans re-fuse (curved-wall sampling doesn't balloon vertex counts; whole-edge cases render bit-identically to before). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): wall elevation picks the slab that supports it, not the highest graze A wall touching a raised slab only at an endpoint no longer lifts entirely to that slab. Elevation selection is now coverage-based: per-slab support is the wall's centerline+face length covered by the slab's rendered footprint minus holes; slabs within 0.1mm of elevation pool their support (party walls spanning two rooms still lift); the wall sits on the highest elevation covering >=50% of its length, else the best-covered group (ties prefer higher), else 0. Sub-5cm grazes are ignored outright. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): interior slab edges meet exactly at the wall centerline Removes the 2cm interior relief inset: both rooms' seam edges project onto the same centerline (or the symmetric sibling midline when no wall backs the seam), so adjacent slabs tile with a shared edge — the 4cm slit under shared walls, visible at its open end on the facade, is gone. Safe against z-fighting because slab side quads are single-sided (FrontSide is enforced repo-wide for the MRT scene pass) and the coincident seam faces have opposite normals; junction step pockets grow to 5cm but remain strictly inside the intersecting wall footprints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): elevation-aware interior slab seams — close the vertical band slit With unequal room elevations, both slabs stopping at the shared wall's centerline left the lower room's half of the wall band open between the lower slab top and the wall base (the wall seats on the higher slab). Interior seams across a wall are now elevation-aware: equal elevations keep the exact centerline seam; unequal elevations project BOTH slabs to the wall face on the lower side — the higher slab runs through the band under the wall, the lower butts the same plane. Wall-less unequal seams keep the sibling midline (a visible step face is correct there). Slab elevation joins the level dependency signature so height edits rebuild sibling slabs live. Verified against real local scenes: all wall bands solid where the seam rule applies (remaining pockets are the known flat-wall-base limitation, tracked separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugin-trees): run wind displacement before the instance transform The r184->r185 upgrade broke the plant wind: r185 fixed TSL's statement emission order so a material's positionNode now runs *after* the instancing transform (r184 emitted it before — verified in the compiled WGSL of both versions). The wind nodes were tuned against the r184 order, reading positionLocal as geometry-local coordinates, so on r185 the displacement moved into level space: sway no longer scaled with the per-instance scale (scaled-down trees thrashed like a storm, leaf cards visibly detaching from branches), leaf phase followed world placement, and STEM_BEND's height term read the floor elevation, so grass/flowers on upper levels slid around rigidly. Restore the r184 semantics explicitly: WindNodeMaterial assigns the wind node to positionLocal inside setupPosition() before super applies the instance transform, instead of using positionNode. The emitted WGSL is statement-for-statement identical to r184's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): resolve slab joins across floor elevations * fix(editor): keep slab resize arrows visible --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a590747748 |
Adjustments pass: three 0.185, undo/cancel semantics, guide & panel fixes (#496)
* fix(editor): only start handle drags on primary button Right-click over a rotation/move/resize handle started the gesture and stopPropagation()'d, fighting the camera orbit. Guard every gesture starter (shared useHandleDrag, group rotate gizmo, wall endpoint/height/ move, fence move, roof trim) with event.button !== 0 before it swallows the event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): default units from timezone/locale until user picks Derive the metric/imperial default from the IANA timezone (US, Liberia, Myanmar zones -> imperial; anything else -> metric), falling back to an explicit locale region subtag only when no timezone resolves. Timezone tracks actual location, unlike navigator.language where en-US is a common default far outside the US. The unit is only persisted once the user explicitly sets it, so an untouched preference keeps tracking location; existing persisted values are treated as explicit and left alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): toggle shadows via renderer.shadowMap.enabled, not castShadow Flipping a light's castShadow at runtime crashes three r184's WebGPU renderer: toggling off disposes the shadow map's GPU texture, but the node builder cache evicts with the post-toggle key, so the shadows-on entry survives still referencing the destroyed texture. Re-enabling reuses that stale state and every frame submit fails with GPUValidationError ("Invalid CommandBuffer from CommandEncoder"). Keep castShadow static and drive the user-facing toggle through the Canvas shadows prop (renderer.shadowMap.enabled), which rebuilds materials without disposing shadow resources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core,editor): floor undo history at scene load Undo could step back past the scene load into the pre-load (empty) state, wiping the whole project — which autosave would then persist. Two defects: clearSceneHistory() had zero call sites, so every load left the empty pre-load state in zundo's pastStates; and setScene wrote the store twice, recording a half-normalized intermediate as a second undo target. - applySceneGraphToEditor, JSON import, and reset-to-default now clear history so the loaded scene is the undo floor - setScene collapses to a single tracked write (final state identical) - clearSceneHistory also resumes tracking so a load landing inside a pause window can't strand undo recording off Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build(three): upgrade runtime to 0.185.1, pin @types/three at 0.184 r185 renames directionToColor/colorToDirection to packNormalToRGB/ unpackRGBToNormal and splits SSGI's packed rgba output into separate AO (getAONode, single channel) and GI (getGINode) textures; wind-node's positionLocal reads become positionGeometry. @types/three stays at 0.184.1: the 0.185 typings send tsgo's inference into unbounded allocation (microsoft/typescript-go#2125 class — it ate ~70GB/90s and OOM-killed the machine). viewer/lib/tsl-compat.ts bridges the two renamed TSL exports with 0.184-typed signatures; drop it and the pin together once tsgo copes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): gate .ktx2 preset-texture loads on detectSupport KTX2Loader.load throws before detectSupport has run, and materials created while a standalone capture canvas's renderer was still initializing cached themselves permanently texture-less — fabric slots rendered white in item thumbnails. .ktx2 loads now await whenKtx2Ready() (resolved by the first successful ensureKtx2Support), which is exported so hosts with standalone canvases can arm it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): accept host-supplied country for the unit default applyCountryUnitDefault lets the host app feed an authoritative IP-derived country (e.g. Vercel's x-vercel-ip-country) into the unit default. Stronger signal than the timezone heuristic applied at store creation, still never overrides an explicit user choice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): box select starts over locked guide images A locked guide's hit-rect swallowed pointer-down via stopPropagation, so marquee selection couldn't start on top of it. Locked guides now let the event bubble to the svg root; click-to-select and the unlock affordance still work because a non-drag release fires onClick as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): live rotation readout while rotating a guide image Rotating a guide with the 2D handles gave no angle feedback. Reuse the registry layer's RotationAngleOverlay (wedge + degree chip) for guide rotate drags: sweeps from the grabbed corner's bearing at grab to its current snapped bearing, suppressed under ~0.5deg so a fresh grab doesn't flash a sliver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): lingo free-text input in the guide set-scale dialog The real-length field accepts natural measurement text via @pascal-app/lingo — 5'11", 180cm, 1m80, 12ft — parsed in the dropdown's unit (a bare number still means that unit, a typed unit wins). A faint '= 1.80 m' hint previews non-trivial input, unparseable text gets a clear error, and the odd onBlur force-reset to 0.0001 is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): cmd+z mid-interaction cancels the gesture instead of undoing Undo pressed while the mouse is mid-action (moving, drawing, dragging a handle) used to history-jump under the live pointer — stale carry, half gestures committing against a rewound scene. Now it reads as 'abort this action', exactly like Escape: - the global undo/redo arms first route through the tool:cancel path (covers build drafts, placement ghosts, move tools) and skip the history jump when anything was in flight (consumed, scope-active, or inputDragging); - pointer drags that only knew pointercancel (generic handle drags, group rotate, wall side/height handles, roof trim) gain the same capture-phase Escape/cmd+z keydown the group-move drags already had — fixing Escape for them too; - the existing capture-phase handlers (3D/2D group move, 2D registry move overlay) additionally accept cmd+z as cancel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): box select arms over any guide image that won't drag Follow-up to cd830279, which only let LOCKED guides bubble pointer-down. An unlocked, unselected guide also swallowed the event for nothing (no translate drag starts), so marquee selection could never start on top of it. Now only the one case that uses the event consumes it — selected + unlocked → translate drag — and everything else bubbles to the svg root. Click-to-select still works: a non-drag release never crosses the box-select threshold, so the trailing click fires the guide's onClick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): cmd+z during draft placement cancels instead of undoing The preset/item placement flow (useDraftNode + placement coordinator) registers no interaction scope and holds no pointer, so the cmd+z cancel guard from c699d74e saw it as idle and history-jumped mid-placement. Paused scene history is the universal tell — the draft cycle (and every adopted-move session) keeps temporal paused for the whole gesture, and an undo against a paused store lands on a stale baseline anyway. Treat !isTracking as in-flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): cmd+z mid-placement completes the cancel, not just skips undo 4ffda723 stopped the history jump during preset/item placement but left the draft alive: the item tool passes no coordinator onCancel — it is Escape's fall-through (switch to select, unmount the tool) that actually destroys the draft. Extract that fall-through and run it from the cmd+z path too whenever a gesture is live and nothing consumed tool:cancel, so cmd+z now behaves exactly like Escape end to end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): node selection clears a lingering reference selection Selecting a guide clears the node selection (handleGuideSelect), but the reverse was never wired: clicking a wall with a floorplan reference selected left selectedReferenceId set, and the panel manager's reference-first priority kept showing the floorplan panel until it was closed by hand. PanelManager now drops the stale reference the moment a scene selection (nodes or zone) appears. Also: the inspector's expanded state is shared across panel swaps by design, but it survived close/reopen too — deselecting everything now resets it, so a fresh selection opens the panel collapsed again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugin-trees): wind displacement reads positionLocal, not positionGeometry The three-0.185 migration renamed positionLocal to positionGeometry in the wind nodes, but positionLocal was never removed in r185 — and the two are not interchangeable here. NodeMaterial.setupPosition applies the instance transform by mutating positionLocal, then overwrites it with positionNode's output; reading raw positionGeometry therefore discarded every instance matrix — leaf cards rendered unscaled at tree-local coordinates (a giant canopy filling the sky) and grass/flower instances collapsed invisibly. Reading positionLocal (instance transform included) restores r184 behavior exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3625d17bfc |
fix(editor): default sidebar to build tab, drop to select on panel close (#495)
The AI/chat tab was the default panel when opening a project; build is the expected starting point. Also, collapsing the sidebar (rail click or drag-collapse on desktop, sheet close on mobile) left the last build tool armed — it now resets to select mode. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
47313263fb |
feat(viewer): rendering & lighting pass — sun-dominant look, sky backdrop, grounded horizon (#493)
* feat(editor): dev-only window hook for deterministic camera poses Exposes a getter for the CameraControls impl in development so screenshot/automation tooling can set exact camera poses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): rendering pass — sun-dominant lighting, grade, gradient-sky IBL, albedo clamp, SSGI tune - Shadow intensity clamp 0.55 → 0.9 (sun no longer leaks into shadow), 2048 shadow maps, PCFSoft filtering - Scene-referred contrast/saturation grade before ACES output (GRADE_PARAMS in post-processing) - Procedural gradient-sky IBL (cool zenith / warm horizon / ground bounce) replaces the venice_sunset HDR fetch; env-only, background unchanged - Near-white albedos clamped to ~0.83 linear (defaults, white palette, catalog preset-white/softwhite, schema presets) - SSGI: 2 slices / 6 steps, radius 1.6, aoIntensity 1.7, giIntensity 2 (bounce on); studio hemi 0.6→0.45, fill 0.75→0.6 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): shadow-caster-only cutaway, glass fresnel, ground fade, specular-map unwiring - SHADOW_ONLY_LAYER (4) + lib/shadow-only.ts: hidden roofs/levels in level-solo (editor) and dollhouse (GLB viewer) stay in the shadow map, so interiors keep sun shadows + window light patches; only the sun's shadow camera enables the layer - Glass: fresnel-driven opacity + envMapIntensity on transparent standard materials (catalog glass, scene glass, window default) - Site ground: radial fade into the theme background at the lot boundary (TSL colorNode); dead ground-occluder.tsx removed - Catalog: 22 bogus *specular*→metalnessMap wirings removed (specular level maps are not metalness; they darkened/metallized dielectrics) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): PCSS contact-hardening sun shadows via LightShadow.filterNode Custom TSL filter: Vogel-disk blocker search (textureLoad — the sampled path would inherit the comparison sampler, which WGSL rejects) → receiver-blocker penumbra estimate → variable-radius rotated PCF. shadow.radius scales max penumbra (now 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): dark-theme recalibration for the new lighting pipeline Night/twilight: lifted ambient/hemi beds (the 0.9 shadow intensity crushed them), brightened theme grounds to a lit mid-tone, and dimmed the daylight gradient-sky IBL to 0.2 for dark appearances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): rendering-pass follow-ups — GI denoise, lighter interactive SSGI, sky gradients, scoped solo shadows - Denoise the SSGI GI bounce (it composited raw — the visible grain) and drop giIntensity to 1 - Interactive SSGI back to 1 slice (×6 steps); SSGI_BAKE_PARAMS (2×6) for the thumbnail/bake pipeline - Per-theme backgroundSky: vertical zenith→horizon backdrop gradient in the post pipeline; makes the lot-edge ground fade read in every theme - Level solo: only levels above the soloed floor stay shadow-caster-only; below-levels plain-hide (they can't block the sun) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): kill SSGI grain, real horizon — infinite ground disc fading into the sky - SSGI back to 2 slices × 6 steps: three's own minimum preset without temporal filtering — 1×6 was below the floor and the grain showed on flat walls; denoise radius 5 on both AO and GI, aoIntensity 1.5; bake preset raised to 3×8 (single-frame renders) - Site renderer: presentation horizon disc (8× lot radius, min 400 m) under the lot in the theme ground colour, fading radially into the theme background; lot fill back to plain ground colour (the disc carries the fade); never pickable (noop raycast) - Backdrop sky gradient compressed to the upper half of the screen so it meets the disc's far fade at exactly the horizon colour — no seam Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): laptop-budget SSGI + seamless world-space horizon - SSGI interactive: 1 slice × 6 steps, GI bounce OFF (denoised AO only) — 2 slices was a thermal problem on laptops; the bounce moves to SSGI_BAKE_PARAMS (3×8, gi 1) for one-shot renders - Horizon disc dissolve: albedo fades to black while emissive fades to the background colour, so the far end IS the backdrop (no lit-vs-flat seam); backdrop gradient graded with the same transform as the scene - Sky gradient is now world-space: per-pixel view ray reconstructed from the scene camera matrices, sky above the true horizon (dir.y 0→0.35), pure background below — aligns with the disc at any camera angle - Studio theme: ground #e9e7e2 / horizon #fbfbfa / sky #dde7ef so the fade is actually visible against the white void Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): kill shadow grain (PCSS opt-in only) and the inked horizon line - The eye-level ground grain was measured (high-pass σ on a flat patch: 2.95 baseline → 1.52 shadows-off → 2.49 ao-off): PCSS's per-pixel IGN dither was the dominant source, and it can't be fixed within a laptop budget without TAA. Interactive shadows revert to the renderer's PCFSoft (clean, cheap); PCSS stays wired behind ?enable=pcss for experiments and future bake-time use - The horizon 'line' was the ink pass edge-detecting the ground disc's depth silhouette against the backdrop. Ink now fades with raw depth (full below ~150 m, gone past ~350 m) — near silhouettes keep their SketchUp line, the horizon dissolves cleanly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): soft blue skies for light presets, slab coplanarity epsilon - backgroundSky retuned: studio/paper/blueprint/verdant get a soft blue zenith (overcast keeps a bluish gray — it's overcast; mediterranean/ sunset were already blue; dark themes untouched) - Slabs duplicated at the exact same position z-fight and no camera near/far tuning can separate identical depths; each slab mesh now gets a deterministic sub-3mm lift hashed from its node id. Render-only — node data, snapping and measurements untouched. (Long-term fix is reversedDepthBuffer; parked in plans with the depth-consumer audit.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): walk back perf-costly rendering-pass pieces, soften dark-theme ink Review feedback on the rendering pass: - SSGI back to main's params (1 slice / 4 steps / r1, AO-only) everywhere, including thumbnails — SSGI_BAKE_PARAMS removed. Kills the added AO grain and the extra per-frame cost; bake workers also stop paying for heavier GI. - Shadows stay visible via plain knobs only (intensity 0.9, radius 4): the custom TSL PCSS filter is gone, shadow map back to 1024, filter back to PCF (r184's Vogel-disk PCF respects radius, so edges stay soft). - Night/twilight ink edges: colour now derived from the theme background (lifted toward white) instead of a near-white constant, and dark scenes run the ink at 70% alpha — no more glowing wireframe on dark backdrops. - Snapshots: the `transparent` capture flag is now honored — preset/item captures keep their alpha, while studio renders and project thumbnails composite the theme background + sky gradient (same world-ray math as the viewport backdrop, uniform-driven so the cached pipeline serves both). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer,nodes): ground the scene — contact vignette + horizon haze, shadow tune Follow-ups from review: - Shadow intensity 0.9 → 0.75 (read too heavy) and PCF radius 4 → 2: the filter's per-pixel dither spreads with radius, which showed as dots across wide penumbras. Both are free knobs. - New shared backdrop formula (viewer lib/backdrop.ts): background below, theme-derived haze band hugging the horizon (background lifted toward white — faint glow on dark themes), sky above. Used by the post pipeline, the thumbnail pipeline, and the site horizon disc, whose far-field dissolve now evaluates the same gradient per fragment view direction — ground and backdrop converge to identical colours, so no horizon seam from any camera pose. - Contact vignette on the horizon disc: a soft albedo darkening hugging the lot (15%, fading out by ~2.6 lot radii) so the parcel sits on the field instead of floating on it. Albedo-only — never tints the dissolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(nodes): make the contact vignette read on bright themes A fixed 15% albedo cut disappears into the tone mapper's shoulder on themes with strong key lights (studio runs intensity 4), and the dissolve's bright emissive diluted what was left. Scale the vignette with the theme's strongest light (0.13×, clamped at 0.45) and apply the halo to the in-band emissive as well — it zeroes out by 2.6R while the dissolve completes at 5R, so the far field stays the pure backdrop and the horizon seam guarantee holds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): no more horizon line, sky gradient reaches the horizon Two things drew a visible line where the infinite ground met the sky: - SSGI AO grows a band along the geometry↔sky depth cliff (same disease the ink pass had). Fade AO to 1 with raw depth over the ink's ≈150→350 m window, in both the viewport and thumbnail pipelines — AO is a near-field cue, it has no business shading the horizon. - The backdrop's flat haze plateau sat between two ramps, which the eye amplifies into Mach lines. The gradient is now one smooth background→sky ramp crossing the horizon, with the haze applied as an exponential glow peaking exactly at dir.y = 0 — C¹-smooth on both sides, edge-free. The sky ramp also starts at the horizon instead of ~11° up, so the theme's backgroundSky blue actually reads at eye level instead of hiding at the top of the frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): three-stop sky — derived deep zenith, warm horizon haze The single pale backgroundSky stop read as a white void with blue hiding at the top of the frame. The backdrop is now three derived stops, inZOI-style: - pale theme sky arrives fast (full by ≈8° elevation), - then deepens toward a zenith colour derived per theme in HSL (saturate ×1.5, darken ×0.72 — hue stays the theme's own: blue studio, lavender sunset, near-black night), - horizon haze now lifts toward a warm white (#fff4de) instead of pure white, giving the junction the slight yellow of sun-scattered atmosphere. All derived from the existing backgroundSky/background fields — no theme data changes, and the shared-formula seam guarantee (viewport = captures = horizon disc) carries over. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): horizon haze derives from the sky, hugs the horizon tighter White-based haze read as a tall white stripe between ground and sky. Aerial perspective is sky-coloured light with a little sun scatter, so the haze now pulls the theme's backgroundSky toward the warm sun tint (50% light themes, 25% dark) and the glow decay tightens (exp −7→−11, weight 0.9→0.8) — the merge band is skyish, sunish, and half the height, so blue starts right above the ground line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c45808840c |
nodes: add modular cabinets and wall trim controls (#461)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * Add modular cabinet node * Add modular cabinet run and tall cabinet workflows * Add cabinet paint slots and material groups * Improve modular cabinet presets and wall snapping * Improve cabinet wall snapping * Fix cabinet wall snapping and countertop joins * Improve cabinet appliance compartments * Improve fridge visuals and smooth cabinet animations Fridge exterior is now brushed silver with brass accents and the interior is all white. Door/drawer animation no longer rebuilds the full cabinet geometry per frame — animated parts carry pose metadata that a new per-frame cabinet system applies directly to transforms. Co-Authored-By: Claude <noreply@anthropic.com> * Improve gas hob flames and cooktop knob controls Port the curved-flame look from reference gas-burner photos: each burner gets a ring of vertex-coloured tube flames (blue body, orange-yellow tips) whose spines breathe and flicker per frame at ~30fps, plus a flat ignition glow and a faded heat halo. Fix the knob pointer notch to rotate with the knob instead of drifting sideways. Also includes cooktop compartment presets/panels and cabinet selection/move affordance work. Co-Authored-By: Claude <noreply@anthropic.com> * Fix cabinet UVs and registry actions * Fix registry move snap typecheck * Fix cabinet knob animation concurrency * Fix cabinet material disposal, undo flood, and 2D move parity - Flag shared cabinet appliance materials (and the viewer's cached material factories) as cached so geometry rebuilds no longer dispose materials still referenced by other nodes, forcing scene-wide shader recompiles. - Route the door/drawer open animation through useLiveNodeOverrides with a single final commit, so one play is one undo step instead of ~20. - Add a cabinet-module floorplanMoveTarget: 2D drags now convert through planToLocal + magneticSnap like the 3D path, instead of writing plan coords into the run-local position (teleporting modules on rotated runs). - Re-key sibling runs (cabinetAdjacencyRevision) when a run's neighbor-affecting inputs change, so countertop overhang joins re-trim when a neighbor moves/resizes/deletes. - Narrow the cabinet panel's scene subscriptions (useShallow module selector) so it stops re-rendering on every scene mutation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Restructure cabinet schema and centralize run math/mutations Prepares the cabinet node for the queued specialty units (corner L-shape, sink base, appliance gap, open shelving): - Add a `moduleKind` discriminator (default 'standard') on CabinetModuleNode so new unit types extend an enum instead of overloading the stack, and split CabinetCompartment into a z.discriminatedUnion so invalid field combos (drawer with rack style, fridge with burner state) are unrepresentable. Shared box fields now come from one `cabinetBoxFields` object so run/module schemas can't drift. - Extract the straight-line run assumption (sort-by-x, edges, adjacency, spans, side-insert, reflow, frame transforms) into run-layout.ts — previously spread across definition/geometry/quick-actions/stack/ move-frame, so corner support would have meant five parallel edits. - Consolidate the run mutations (add module, wall cabinet/hood above, base↔tall switch, layout-revision bump) into run-ops.ts on SceneApi. Panel and quick-actions had drifted copies: the panel's add-module skipped gap checks, revision-bump scope differed per surface. - Remove dead code: node-level doorStyle (with a migrateNodes entry — geometry only ever read per-compartment doorType), the unreachable slot-handle mesh block, the no-op handlePosition 'edge' enum value, wallLocalX, and duplicate totalCabinetHeight definitions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add cabinet snap tests and split the cabinet panel - New tests: cabinetModuleParentFrame.magneticSnap (flush snap, threshold, Z alignment, nearest-edge) and wall-snap's resolveCabinetWallFaceOffset / collectCabinetWallSnapNeighbors (straight face, miter taper, ray-miss fallback, yaw/face/parent filtering) — 87 cabinet tests, up from 71. - De-brittle geometry test lookups: coordinate-encoded mesh names are now matched by pattern, so dimension-default changes don't break them. - Split panel.tsx (1,541 → 679 lines): CompartmentCard + option constants into compartment-card.tsx, CabinetRunPanel + reflow wiring into run-panel.tsx, and the compartment type-transition tables into a pure resolveCompartmentTransition in stack-transitions.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Split cabinet geometry.ts into per-appliance modules Pure code motion: geometry.ts (4,728 lines) becomes a 336-line orchestrator dispatching into geometry/ — shared helpers + cached appliance materials (shared.ts), CSG fronts/handles/doors/drawers (fronts.ts), run spans + countertop (run.ts), and self-contained hood/fridge/cooktop/dishwasher/ oven-microwave/pantry builders. No mesh names, userData stamps, materials, or math changed; the './geometry' import path is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add undermount sink compartment with CSG-cut countertops New 'sink' compartment type (single / double / 60-40 bowl layouts) following the cooktop pattern: zero stack height, countertop-plane geometry, panel card with a bowl-layout selector, and a Sink Base preset. Bowl openings are subtracted from both module- and run-owned countertop slabs via three-bvh-csg; the basin shells, drain plumbing, and a spec-sheet-proportioned gooseneck faucet (Grohe Minta-style pin lever) render beneath the cut. Sink modules skip the carcass top panel and the deck under the sink row so the basin hangs through. Also scope the viewer mock in roof-surface-placement-guides.test.ts to only stub useViewer — the bare module stub leaked into later suites and broke real CSG imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update ifc-converter next-env reference to build-mode route types Regenerated by `next typegen` during check-types; points at .next/types instead of the dev-mode .next/dev/types path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Draw cabinet floor plans with kitchen drafting symbols Replace the per-module box soup with NKBA/architectural-convention symbols: the run draws one heavier countertop outline per span (extended by the overhang), base modules draw their front edge plus compartment symbols — rounded bowl rects + faucet dot for sinks, burner/zone rings for cooktops (reusing the 3D layout tables so 2D and 3D always agree) — and appliance modules carry standard upright labels (DW / REF / OV / MW / PAN). Nested wall cabinets and hood-only modules draw as dashed open outlines per the above-cut-plane convention. * Play cabinet open/close animation on the E interaction key Add an `e` slot to registry KeyboardActions and dispatch it in the keyboard hook ahead of the legacy door/window arms, so kinds opt into the E interaction on their NodeDefinition. Cabinets register it: E on a module eases its doors/drawers open or closed, E on a run swings every child module together, and hood-only modules fall through. The panel's rAF animator moves into cabinet/interaction.ts (live-override frames, single undo commit) so the Play button and E share one animation, and the shortcuts dialog now documents E. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add kitchen island and bar counter support to cabinet runs Islands: run-level countertopBackOverhang (seating side) and withFinishedBack (decorative back panel), plus an I-key island placement mode in the cabinet tool that skips wall snap. Bar counters: optional barLedge {edge, height, depth} drawing a knee wall and raised slab along the back or either run end, superseding the seating overhang on its edge. withWaterfall drops slab-material panels to the floor on exposed run ends. 2D floorplan outlines, selection bounds, geometry keys, and run panel controls updated in parity; schemaVersion 3 -> 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Merge quick-action arrow and plus into one directional add glyph The add-left/add-right buttons showed two unrelated icons; a single plus-on-tail arrow reads as "add on this side". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Extend plugin discovery instead of replacing the host source Also track the ifc-converter next-env routes reference update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add L-corner runs to modular cabinets with run-scoped grouping An "L Left/Right" quick action spawns a perpendicular base leg, wall leg, and wall bridge as metadata-linked runs parented to the SOURCE RUN, so the run stays the single group: selecting a base module selects only it, and the clicked module never becomes a container. Corner runs re-anchor when the source module resizes or moves (new MovableParentFrame.onCommit hook, mirrored in the 2D floorplan commit), run bounds fold in child leg runs, and deleting one corner member removes only that node while unlink patches keep the survivors' link metadata consistent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Delete emptied cabinet run groups in the same delete gesture Removing a run's last child (module or derived corner leg) left an empty group node in the scene graph and persisted data. onDeleteCascade now receives the gesture's pending delete ids so multi-select deletes count siblings as gone, and both cabinet kinds cascade the orphaned parent run away in one undo step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remove filler front gap treatment * Apply architecture review fixes across cabinet, plugin-trees, and framework Move cabinet semantics out of packages/editor behind registry capabilities (def.tree, selectionProxy.bypassDirectPick, IconRef quick-action icons, catalog tool field), route cabinet selection through a core selection-proxy helper, and make PanelWorkspace an opaque host tag. Fix the cabinet placement undo flood, dirtyNodes.add bypasses, normalized UVs on paintable slots, plugin-trees ghost layers/stale level transforms/untyped find-sync event, and cross-file store-mock pollution plus orphaned dist tests that broke combined test runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Propagate front styling to L-corner runs even when re-layout bails Group style changes rode entirely on syncDerivedCornerRun, which bails silently when a later-drawn wall blocks the corner layout or a leg run gained modules outside the derived-run spec — leaving the L legs styled stale. Apply the style patch directly to every linked corner run before attempting the geometric re-layout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Improve editor viewer integration * Refactor plugin panels and floorplan invalidation * UV-unwrap and material-assign roof-segment trim planes * Fix cabinet placement snap feedback * Add cabinet wall snap bypass * Fix cabinet continuous placement flow * Implement wall band material slots * Add corner addition preview functionality and related tests * Fix cabinet and wall band behavior * Fix architecture review blockers * Fix post-merge quality check * fix cursor review feedback * Fix cabinet placement type and quick action history * Improve cabinet placement feedback * Make wall bands configurable * Add wall trim profiles and default materials * Fix architecture review issues * Fix cabinet rotation and wall band visibility * Fix sink appliance paint persistence * Bake cabinet animations and preserve wall band material * Fix cabinet group duplication and module snapping * Fix cabinet duplication architecture * Fix cabinet floorplan test and make placement-type hint a live chip - floorplan.test.ts: pass the required liveTransforms map to cabinetFloorplanSiblingOverrides (the runtime callers always supply it) - ToolHint gains an optional generic `chip` contract (subscribe/value/ cycle/labels) so a kind can render a tool hint as a live mode chip — like the snapping/continuation chips — without the editor knowing the kind's store - cabinet: the I hint now shows the current value (Type: Cabinet / Type: Island) and is clickable to toggle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wassim SAMAD <wass08@gmail.com> |
||
|
|
7d6879c416 |
Merge pull request #487 from pascalorg/fix/compass-follow-hidden-align-north
fix(editor): compass needle follows align-north in 3D-only view |
||
|
|
cbcc705173 |
fix(editor): animate compass needle on align-north in 3D-only view
When the 2D panel is hidden, align-north publishes a single '2d' pose that the camera applies through the echo-suppressed pending-pose path, so no '3d' poses ever reach the compass subscription and the needle stayed frozen. Drive the needle with a local rAF exponential decay (same 90ms time constant as the 2D view animation and the camera's effective smoothTime); a live '3d' pose cancels it and takes back ownership. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5e5b62b09e |
fix(viewer): wall-clock scene-ready cap + skip unreachable dirty nodes (#484)
Two readiness fixes surfaced by the first successful headless bake of a heavy scene (which exported with 33 items missing): - The 180-FRAME give-up cap silently assumed display-rate frames. The bake page's timer-driven loop (?disable=draw) runs those frames in ~3.6s — shorter than a cold item-model download — so the cap fired and the export raced ahead of real content. New `sceneReadyMaxWaitMs` Viewer prop replaces the frame cap with a wall-clock one on hosts whose frame cadence is decoupled from time; default frame behavior unchanged for interactive surfaces. - hasPendingSceneBuildWork now skips UNREACHABLE dirty nodes: orphans whose parent doesn't list them in `children` (or parentless non-roots) never render — every renderer enumerates the parent's children array — so no system ever builds them or clears their mark. Observed in prod scene data (two dangling windows parented to a level that doesn't list them): they held every bake of that scene to the full readiness cap. Validated: the 200+-item office scene with ?disable=draw settles organically in ~2s and exports the complete artifact; a one-shot 504'd item is now WAITED for through its retry instead of racing the cap. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
aa30899090 |
fix(viewer): drive the frame loop with a timer when ?disable=draw (#483)
With no real frames submitted, Chromium's no-damage scheduler throttles requestAnimationFrame to 1Hz on Linux — measured in the headless bake worker: every useFrame system ticked once per second, so a heavy scene needed 300+ seconds of wall time just to run ~300 frames of build work (and exceeded the capture deadline on the worker's slower cores). The empty-scene clear from #482 avoids this on macOS but not on Linux. When `draw` is disabled, drive advance() with setInterval instead of rAF — plain timers are never throttled on a visible page, so the loop pacing is deterministic (fps prop) regardless of compositor heuristics. Normal rendering paths are untouched. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bbe0e2b0b5 |
feat(viewer): ?disable=draw — skip scene rendering for graph-only consumers (#482)
The headless bake worker renders on SwiftShader (CPU), where per-frame vertex/draw cost dominates the whole capture — but a bake only reads the built scene graph; the pixels are thrown away. `?disable=draw` (same diagnostic-param family as postFx) renders an EMPTY scene each frame instead of the real one: useFrame systems and scene-ready keep ticking, per-frame cost collapses to a 64x64 clear. Rendering nothing at all does NOT work: with zero submitted frames Chromium's no-damage scheduler throttles rAF to exactly 1Hz (measured), starving the very systems the bake needs — hence the empty-scene draw. Measured on the office scene (200+ items) with a real GPU: capture 45.0s -> 8.4s (settle 37.6s -> 4.5s); output equivalent to a normal render (structural GLB diff clean; the ±13-accessor variance exists between normal runs too). CPU-only (worker) numbers via the prod-image container against a preview deploy. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
352954aafc |
editor: group manipulation — Photoshop-style multi-selection move, rotate, duplicate (#481)
* feat(editor): group R/T — keyboard rotate for multi-selections Extract the rigid rotate/translate math from the group gizmos into group-transform-shared (rotateGroupPatches / translateGroupPatches) and add a keyboard group-rotate path: R/T on a multi-selection steps the whole group ±45° around its bbox center, welded junctions and connected-component expansion included, committed as one updateNodes batch (one undo step). Single-selection R/T arms (reference, door/window flip, registry keyboardActions) are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): polygon participant kind — slabs/ceilings/zones join group transforms classifyParticipant learns a 'polygon' kind ([x,z] vertex arrays + optional hole rings): slab/ceiling/zone now ride along in group move and rotate — the two 3D gizmos and the keyboard group R/T all flow through the shared rotateGroupPatches/translateGroupPatches, so the wiring is the classifier plus the rotate gizmo's spread sampling. Zone's 3D renderer merges its live polygon override so it previews during the drag (slab/ceiling already rebuild via getEffectiveNode in their systems). Classifier + patch unit tests included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): 2D floorplan group drag — Photoshop-style multi-selection move Plain pointer-down on a transformable member of a multi-selection now slides the whole selection rigidly in the floor plan (the 2D parity fill for the 3D GroupMoveHandle), instead of collapsing the selection to the pressed node. A plain click (no drag) still collapses on release; modified clicks, Cmd-drag direct move, and reshape affordances keep their paths. The move-handle dot routes through the same group session when its node is part of the multi-selection. The session shares group-transform-shared verbatim: welded LinkedNeighbor endpoints, connected-component expansion, live previews via useLiveNodeOverrides.setMany, grid snapping on the delta plus Figma alignment through applyFloorplanAlignment (guides in every mode except off, magnetic pull in lines mode), interaction scope handle-drag with the shared group-move label, and a single batched updateNodes (one undo). A dashed group bbox overlay shows what rides along and tracks the live delta. clientToPlan moves to lib/floorplan/plan-coords for reuse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): group manipulation polish — helper text + shortcuts dialog Multi-selection select-mode HUD now advertises the group gestures (drag any member to move the whole selection, R/T to step-rotate), and the keyboard-shortcuts dialog documents both under Selection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): restore zone renderer useLiveNodeOverrides import Formatter stripped it between the import and usage edits; nodes' tsc --build catches it where the cached turbo typecheck did not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): tag the 2D group selection box for drivability data-group-selection-box on the overlay <g> so smoke drivers and devtools can assert the multi-selection chrome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): multi-selection shows highlight only — hide per-node edit chrome While 2+ nodes are selected the group is manipulated as one rigid piece, so per-member edit affordances hide in both views: the 2D overlay pass strips handle/dimension chrome centrally (stripHandleChrome — body highlight, hit targets, and zone name text stay), and in 3D the slab / ceiling boundary editors and slab hole click-to-edit outlines mount only for a sole selection (the arrow-handle rig, wall side arrows, selection affordances, and floating menu were already single-gated). Transformable members show a move cursor in the floor plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): 3D body-drag group move replaces the move gizmo cross Pressing any transformable member of a multi-selection in 3D and dragging past the threshold now slides the whole selection on the ground plane (group-move-3d.ts, armed from the selection manager's pointer-down choke point) — same participant snapshot, welded junctions, grid + alignment snapping, live overrides, and single-undo commit as the 2D session; the 2D dashed bbox rides the same delta in split view. A plain click still collapses the selection to the pressed node. The purple GroupMoveHandle cross is deleted, and the hover cursor advertises the gesture: 'move' over any transformable member in both views (3D selection-manager cursor + 2D entry cursor). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): group action menu — move / duplicate / delete the whole selection Multi-selections get a floating Move / Duplicate / Delete pill in BOTH views (GroupFloatingActionMenu anchored above the 3D group bbox, FloorplanGroupActionMenu anchored above the 2D dashed group box); the single-node menus are now sole-selection only. All three actions target the whole selection via shared group-actions: - Move starts a group pick-up: the selection rides the cursor (delta-relative, grid + alignment snapped) across BOTH surfaces — floorplan via the scene CTM, 3D via a ground-plane raycast through a three-context bridge published by the selection manager — until a click places it as one undo step; Escape / right-click cancels. - Duplicate clones the selection through the clipboard pipeline (subtrees + id remap, without touching the user's clipboard — new duplicateNodesToLevel), selects the clones, and picks them up; cancelling deletes the clones again. The pick-up scopes to the selection (no component expansion / welded links) because the clones sit exactly on the originals, and falls back to participant-data bounds because the clones' meshes haven't mounted yet. - Delete removes everything selected with the keyboard arm's bulk-confirm semantics. Verified end-to-end with the scripted smoke (16/16): chrome hiding, 2D/3D/split group drags, R/T round-trip, one-step undo, menu move / duplicate / delete, 3D move cursor. (Headless runs of the suite can deadlock in SwiftShader's GL readback on the split-view resize — environment-only; run headed.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): group transforms no longer re-create auto rooms Every group commit (2D/3D body drags, pick-up, rotate gizmo, keyboard R/T) now wraps its single updateNodes in pauseSpaceDetection / resumeSpaceDetection: the wall-driven room auto-detection rolls its baseline forward instead of treating rigidly-moved walls as a new room and duplicating its floors/ceilings. Room creation stays where it belongs — building and editing walls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): polygon hosts carry their attached items in group transforms Ceiling-mounted items are children of the ceiling, but polygon hosts (slab/ceiling) move by rewriting vertices — no group transform — so, unlike wall children which ride the wall mesh, their children were left behind by group moves/rotates. collectParticipants now snapshots a polygon participant's positioned children (level-frame coords, since the host group sits at the origin) so they ride every group path. Unit test included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): click picks up the group; the 2D dashed box is the drag handle Clicking a selected member of a multi-selection (no drag) now enters the group pick-up — the same click-to-move the sole-selection has — instead of collapsing the selection, in 2D and 3D alike; clicking outside still deselects. The 2D dashed selection box is itself the group's handle: move cursor across its whole area, press-drag anywhere inside slides the group, a plain click picks it up, and holding Cmd/Ctrl/Shift lets clicks pass through to the entries so membership toggling keeps working. HUD hint + shortcuts dialog updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): 3D dashed group selection box doubles as the drag handle The multi-selection now shows a dashed wireframe box in 3D (sibling of the 2D dashed rect): move cursor across its whole volume, press-drag anywhere on it slides the group, a plain click picks it up, and holding a selection modifier passes the press through so members inside can still be toggled. Rides the shared live-drag delta so it tracks the group mid-gesture in both panes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): mid-move R/T, 2D corner rotate, realtime opening symbols, box cursor + Esc hint - R/T while carrying a group (body drag or pick-up) now rotates the carried snapshots around the rest pivot — identical to the idle keyboard rotate — instead of leaking into the store mid-session; the session keydown owns the keys in capture phase and the group-move HUD advertises them. - The 2D dashed box grows corner rotate handles: drag a corner to spin the group in 15° steps, Shift for free rotation — the floor-plan sibling of the 3D rotate gizmo, sharing its scope, HUD hints, and single-undo + space-detection-paused commit. - Door / window floor-plan symbols now track wall drags in realtime: their defs merge the walls' live overrides (wallFloorplanSiblingOverrides) so ctx.parent is the effective wall instead of the stale store one. - The 3D selection box asserts the move cursor across its whole volume on pointer move (the old ''-guard lost to app default cursors). - Multi-select HUD + shortcuts dialog mention Esc / click-outside to clear, and the mid-move rotate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): rotate cursor + spinning box on 2D corner rotate; Delete ends a carry - The 2D corner handles show a proper curved-arrow rotate cursor (inline SVG data URI, black glyph with white halo, grab fallback). - The dashed selection box spins live with the group during a corner rotate: the rotate session publishes pivot+angle through the shared drag store and the box applies the SVG rotate transform. - Delete / Backspace during any group session (drag, rotate, pick-up) reverts the session first and then lets the global Delete arm remove the selection — no more dangling carry after deleting mid-move; the duplicate flow's cancel still discards the clones instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): per-node direct manipulation stands down for multi-selections Cmd is both the selection-toggle key and the single-node direct-move / direct-rotate trigger, so a Cmd+click that wobbled past the drag threshold over a selected member (slab or any kind) armed the legacy single-node move and yanked that one member out of the group. All four per-node direct-manipulation entry points — the 3D Cmd-drag move and Cmd+right-drag rotate in the selection manager, and their 2D siblings in the registry layer — now require the pressed node to be the SOLE selection; with a multi-selection the gesture is simply not registered (the group sessions own plain drags, Cmd+click keeps toggling). The shortcuts dialog notes the single-selection scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): group gestures own their ending pointerup — no synthesized click leak use-node-events synthesizes a selection click on EVERY canvas pointerup (deliberately more forgiving than R3F's onClick). Body drags were safe only because inputDragging suppresses it, but the pick-up flows leaked it as a race: the release that started a pick-up could collapse the multi-selection to the pressed node first (then only that node was carried), and the placement press re-selected whatever sat under the cursor after commit. The group sessions now claim their gesture-ending pointer events in the CAPTURE phase and stop propagation, so the canvas never sees them and no click is synthesized: the pick-up claims the placement press on pointerdown and commits on its pointerup; the 3D and 2D drag/rotate sessions stop their ending pointerup (split view: a 2D-started release over the 3D canvas had the same hole). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): screen-rect marquee tests projected oriented bounds, not double-inflated AABBs The 3D screen-rect box select intersected the marquee with each node's world AABB re-boxed in screen space — two nested axis-aligned inflations. Any oblique camera padded every node's claim, and rotated geometry (now common: group rotation) made it flagrant — a diagonal wall's world AABB spans a full square, so marquees selected objects visually far from the cursor. Membership now tests the marquee against the convex hull of the node's ORIENTED (local-frame) bounding box projected to screen — tight under any rotation and camera, uniform for every kind. Pure helpers (convexHull2D / rectIntersectsHull) live in marquee-geometry.ts with unit tests covering the diagonal-wall regression, containment both ways, and edge-only crossings. The plane marquee variant was already precise and is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): keep box select alive after group gestures The capture-phase stopPropagation the sessions used to silence the canvas's synthesized selection click also killed the window bubble listeners that clear the box-select pointer suppression — and the mouse pointerId is constant, so one group gesture left the marquee dead until blur. The sessions now use the designed suppression instead: they hold inputDragging raised through the release event's dispatch (lowered on a 0ms timer) so use-node-events suppresses the click itself, and every other pointerup listener runs normally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): marquee membership by plan footprint; 2D item highlight; site line steps back - The 3D screen-rect marquee now projects itself onto the active level's floor plane and tests the DATA kinds exactly there — walls/fences by their segment, slabs/ceilings/zones by their polygon — matching the plane-marquee tool's semantics. This kills the two remaining imprecision sources the hull pass couldn't: vertex-baked rotation (polygon/fence geometry lives in level coords, so after a group rotation even the local bbox is an inflated axis-aligned square) and ceiling parallax (an elevated plane projects to a screen quad offset from its room, selecting 'from far'). Mesh-transform kinds (items, columns) keep the projected oriented-bbox hull test; plan-space segment/polygon intersection helpers join marquee-geometry.ts with unit tests. - Selected items finally read as selected in the floor plan: palette stroke + heavier weight on the footprint, plus a selection ring drawn above the thumbnail (the move dot used to be the only cue, and it hides in multi-selections). - The site's dashed property line drops to 20% opacity while a multi or in-flight marquee selection exists so it stops fighting the dashed group selection box. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): 2D marquee plan-footprint membership; item marquee preview; overlays re-measure after undo - The 2D screen marquee intersected each entry's getBoundingClientRect — an axis-aligned DOM box, inflated for rotated polygons and diagonal walls (the same double-inflation class as the 3D bug). It now maps the marquee through the scene CTM into a plan quad (rotated views included) and tests the data kinds exactly — walls/fences by segment, slab/ceiling/zone by polygon — reusing the shared marquee-geometry helpers; other kinds keep the DOM-rect fallback. - Items now show the marquee preview tint in 2D (highlighted viewState = the same palette stroke + thumbnail ring the selection shows, slightly lighter), instead of giving zero feedback until the drop. - Everything that measures the selection's meshes (2D/3D dashed boxes, the 3D group menu anchor, the rotate gizmo pivot) re-measures once the meshes settle after a scene change via useMeshSettleEpoch — an undo after a move no longer leaves the box at the pre-undo position (computeGroupBox reads mesh world bounds, which lag the store commit by a frame or two). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): dim the actual site boundary line during multi-selections The earlier dimming targeted registry-layer site entries, but the site lives at the scene root — outside the level DFS and the building-scoped sweep — so no entry ever matched and the dashed property line kept full opacity next to the dashed group selection box. Move the dimming to FloorplanSiteLayer (the component that actually draws the line) and drop the dead registry plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
feeabf4bd3 |
fix(items): retry failed model loads, settle skipped items, keep exports clean (#480)
* fix(items): retry failed model loads, settle skipped items, keep exports clean A transient storage failure (observed: Supabase 504s under the bake page's ~200-concurrent-request burst) permanently broke an item for the whole session: drei's useGLTF caches the rejected load by URL, the per-item ErrorBoundary swallowed it, and the red debug-box fallback was BAKED into the exported GLB (observed in a prod artifact). Meanwhile ItemSystem cleared the dirty mark at group registration — before the model resolved — so scene-ready could fire while GLBs were still loading, risking exported placeholder geometry. - ModelWithRetry: bounded retries (1s/3s) that clear the useGLTF cache entry and re-mount via the boundary's new resetKey. The timer is owned by an effect keyed on the failure count, so StrictMode's synthetic unmount/remount re-arms it instead of silently discarding it (the naive onError-scheduled timer died exactly that way in dev). - Exhausted retries settle the item as SKIPPED: the debug box renders nothing during exports, and the failure lands in useViewer.itemLoadFailures (nodeId -> url) so a bake host can persist which items are missing from the artifact. - ItemSystem holds the dirty mark until the item settles (model mounted, terminally failed, or never expected) — scene-ready now genuinely waits for item content; loading placeholders also hide during exports. - ErrorBoundary: onError + resetKey props. Verified against a 200+-item prod scene locally: permanent 504 -> exactly 3 fetch attempts, bake completes without the item and without debug boxes; 504-once -> retry heals, artifact byte-equivalent to the intact run; no-failure runs unchanged (demo_1 byte-identical). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(items): reset retry budget + settled flag when the asset URL changes Bugbot: after a terminal load failure, swapping the item's model kept the stale failures/epoch state and the settled flag — the new URL never even attempted to load. ModelWithRetry is now keyed by asset src (clean retry budget per URL) and un-settles the item on mount so the replacement load is awaited by ItemSystem/scene-ready too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5831b7b234 |
Merge pull request #479 from pascalorg/feat/smart-unit-suffix
editor: smart unit suffix — dim on focus, yield to explicit units |
||
|
|
2d4e7be709 |
Merge pull request #478 from pascalorg/fix/compass-sync
editor: compass sync + fast align-to-north |
||
|
|
5b4b267a40 |
feat(editor): smart unit suffix — dim on focus, yield to explicit units
While editing a numeric field, the unit suffix now visually responds to the draft content: - Plain number / empty: suffix dims to 40% opacity (signals "default unit if you commit a bare number") - Explicit unit typed (hint showing): suffix fades to opacity-0 over 150ms (the conversion hint provides unit feedback instead) - Unparseable text: suffix stays dimmed (field will revert on commit) - At rest: unchanged full-opacity suffix Uses opacity-0 rather than unmounting so the flex layout doesn't jump. The predicate is simply `hint !== null` — it aligns exactly with measurementHint's internal PLAIN_NUMBER guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
58339c64f5 |
fix(plugin-trees): consume dirty marks so scene-ready fires for plant scenes (#476)
Instanced plant kinds (trees/grass/flowers) never cleared their dirty marks: FloorElevationSystem deliberately leaves the mark for kinds with a def.system, expecting that system to clear it after its own work, but InstancedKindSystem never participated in the dirty protocol. The marks lived forever, hasPendingSceneBuildWork() never went false, and the Viewer's scene-ready signal stalled at SCENE_READY_MAX_WAIT_FRAMES on every plant-containing scene — measured on the headless bake worker as ~190s (180 frames x ~1s SwiftShader frames) of pure cap-wait per bake. Clear the marks in a priority-2 useFrame pass: after the priority-1 floor-elevation lift in the same frame, and only for nodes whose proxy is registered (instances rebuild synchronously from the store, so a rendered node is already built). Validated by baking Wawa House locally: settle 22.5s -> 11.6s (the remainder is genuine asset loading), exported GLB byte-identical with and without the fix. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d4f1053528 |
fix(editor): compass sync review fixes
Route all pending-pose cancellations through clearPendingFloorplanNavigationPose() so savedSmoothTime is always restored. Add a controlstart listener to catch right-click orbit and other pointer drags that weren't handled. Guard the rotation-degree ref mirror to prevent React re-renders from clobbering the imperative 3D-owned value when the panel is hidden, and add a useLayoutEffect to keep the needle DOM in sync. Make the catch-up effect re-run directly on panel reopen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cb5cfc7e40 |
fix(editor): compass sync + fast align-to-north
- Remove CSS transition on compass needle SVG so rotation tracks exactly without rubber-band lag (was transition-transform 150ms) - Drive compass needle imperatively via ref when floorplan panel is hidden (3D-only mode), avoiding per-frame React re-renders of the full SVG while keeping the needle live - Align-to-north in 3D-only mode derives from latestNavigationSyncPose instead of stale viewport, publishes directly to the 3D camera - Temporarily lower camera-controls smoothTime from 0.25s to 0.18s during 2D-origin transitions (SmoothDamp τ≈90ms matches the 2D exponential decay), restoring on completion or cancellation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a477c4ade3 |
Merge pull request #477 from pascalorg/fix/unit-labels-lingo-0.2
fix(editor): area readouts + generated names follow the unit toggle; bump lingo to 0.2.0 |
||
|
|
8476a991f4 |
chore(deps): bump @pascal-app/lingo to 0.2.0
0.2.0 is API-compatible with our usage (parseQuantity, quantity, Kind); it adds i18n/locale entrypoints and does not change the symbols we import. Verified: @pascal-app/editor and @pascal-app/mcp typecheck, and all 31 measurement tests pass, including the AMBIGUOUS_NUMBER escalate guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
682c023be1 |
fix(editor): area readouts and generated names follow the unit toggle
The metric/imperial preference was already honored by every length input, but two area surfaces were still hardcoded to m²: - the "Load build" import dialog's Floor area stat - the auto-generated default names for zones, slabs, and ceilings (e.g. "Zone (12.3m²)"), which are live display fallbacks Both now convert value + label to the active unit. Adds shared squareMetersToAreaUnit / getAreaUnitLabel / formatAreaLabel helpers to lib/measurements (with tests) and routes the site-panel Area readout through them, replacing an inline 10.7639 magic constant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fb0b78c091 |
fix(editor): floorplan reference pass — north convention, cursors, R/T, Set Scale UX (#475)
Fix pass over 2D reference-image (guide) handling plus two adjacent repairs: - North is now world −Z (FLOORPLAN_VIEW_ROTATION_DEG 90 → 0, mirrored in world-grid-snap + mcp README; floorplan-panel de-dupes its local copy). A rotation-0 north-up scan reads upright when the compass says aligned, and "align north" maps to camera azimuth 0 instead of a 90° jump. - Guide resize cursor: custom double-arrow cursor aimed at the dragged corner in screen space — accounts for image aspect (was atan2(±1,±1), square-only), the guide's own rotation, and the floorplan view rotation the overlay group renders inside (was ignored entirely). - R/T rotate a selected reference (guide/scan) in ±45° steps; references live in selectedReferenceId, not the viewer selection, so both arms get the reference-first branch the Delete arm already had. Locked guides skip. - 2D-only mode hides the Top View button (drives the display:none 3D camera); orbit stays — it spins the synced floorplan view. - Set Scale UX: locked guides stay clickable (calibration auto-lock made them unselectable until reload); starting the flow from 3D switches to 2D; the length input pre-fills the drawn length in the pre-selected unit (imperial pre-filled meters labeled feet); Set Scale flips into Cancel while the flow runs (mirrored via referenceScaleActiveGuideId); Hide/ Clear Scale only render once calibrated; Escape cancels (global arm + dialog); Clear Scale and Replace Image drop the calibration auto-lock; discoverability: corner-hint row + panel nudge for uncalibrated guides. - healSceneNodes: strip child refs whose child's parentId names another parent (stale reparent leftovers rendered a window twice — duplicate React keys in 2D, doubled hosted geometry in 3D) and same-array dupes. - Editor accepts onLoaderChange so hosts can measure open-to-interactive time (community wires it to a PostHog timing event). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2cfce3f6b5 |
Merge pull request #472 from pascalorg/feat/lingo-natural-inputs
feat: natural-language measurement inputs (UI + MCP tools) via @pascal-app/lingo |
||
|
|
5ffde5f66a |
fix(editor): site-panel property-line coords + area/perimeter follow the unit toggle
The property-line vertex X/Z inputs and the Area/Perimeter readout were hardcoded to meters. Convert them to feet / ft² when the viewer preference is imperial, matching every other length input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
69813a6e62 |
feat(editor): honor the metric/imperial toggle in every length input
SliderControl (which drives the auto-inspector's ~144 fields and most custom panels) previously ignored the viewer unit preference and always rendered raw meters with a static "m" label, while MetricControl converted to feet — so toggling imperial changed some length inputs and not others. Extract the conversion into a shared `useLinearDisplay(unit, precision)` hook and route BOTH controls through it, so every `unit="m"` length field displays and edits in feet when imperial and meters when metric — consistently. Values are always stored in meters; only display, the text field, and drag/ wheel/arrow deltas move to the display unit. For metric and non-length units (°, %, in, …) the conversions are the identity, so those paths are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
717c2c5c0a |
fix: bug pass — legacy window crash, plant exports, zone snapping/visibility, HDR (#473)
* fix(core): apply window schema defaults on scene load Windows saved before a schema field existed (columnRatios/rowRatios/ frameThickness/…) loaded with those fields missing; the window mesh builder reads them unconditionally and threw every frame, crashing the viewer on legacy scenes. Zod-parse windows on load like doors so the schema defaults land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): mount plant geometry during client GLB/STL/OBJ export The client export never set isExporting, so instanced kinds (trees/ flowers/grass) kept their colorWrite:false raycast collider mounted and exported it as an opaque white box, while the real geometry (which only mounts while exporting) was never captured. Reuse BakeExporter's flag + frame-wait dance in ExportManager, and harden isRenderableMesh to drop colorWrite:false materials from exports entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): route zone drawing through the shared surface snap The zone tool quantized later vertices by distance along a free ray instead of snapping to the grid, and never joined the magnetic wall-corner/midpoint/crossing + alignment-guide pipeline that walls, slabs and ceilings use. Give zone the slab treatment in the 3D tool and both 2D floorplan branches (move + click + double-click commit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): self-host the scene environment HDR drei's preset="sunset" fetches venice_sunset_1k.hdr from raw.githack.com, which intermittently fails ("Could not load venice_sunset_1k.hdr: Failed to fetch"). Point Environment at /hdri/venice_sunset_1k.hdr and ship the file in the app's public/ — same mirroring convention as /audios/sfx; consuming apps must carry the file too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): unmount zones entirely outside the zones layer Zone labels showed in every editing mode (visual noise), and each zone kept a drei <Html> mounted at opacity 0 — an <Html> costs per-frame matrix work and live DOM even when invisible. A new viewer presentation flag (showZones, default true) lets the editor unmount zone meshes and labels whenever the structure layer isn't 'zones' (and during snapshot capture); the registered group stays so zones keep their scene identity for selection and GLB export. Preview / first-person / viewer surfaces are untouched. Raycast-disable moved from a one-shot group flag to per-frame on the meshes, which now remount on layer toggles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af1fe6a34c |
chore(deps): add @pascal-app/lingo to the lockfile; note duplicated helper
Records @pascal-app/lingo@^0.1.0 in bun.lock (added to @pascal-app/editor and @pascal-app/mcp), so `bun install --frozen-lockfile` in CI resolves it. Also documents that measurement.ts is intentionally duplicated with the AI-chat copy across the submodule boundary and must be kept in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3273aac551 |
fix(mcp): restore >0 validation and reject ambiguous numbers in measurement()
Adversarial-review follow-ups to the lingo measurement() field:
- Add a `positive` option (strict > 0) and use it for the non-zero dimension
params. Swapping z.number().positive() → measurement(..,{min:0}) had started
admitting 0 (the core node schemas have no positivity backstop), so a zero-size
wall/opening/roof could be created. Inclusive-0 fields (overhang, sill height,
knee-wall height, opening offset, roof pitch) keep min:0.
- Escalate AMBIGUOUS_NUMBER to error so "1,234" fails instead of silently reading
as 1234 — a 1000x hazard for European decimals. Matches lingo's own /ai fields.
- roofLevelElevation reverted to z.number() (it's a level ordinal, not meters);
radians fields now advertise radian-appropriate examples.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f66dcd438b |
feat(mcp): natural-language measurements for tool inputs via @pascal-app/lingo
Measurement/angle parameters on the scene tools now accept a bare number OR a
natural-language string ("6 in", "180cm", "45°", "1.57rad"), canonicalized to
the unit the handler already expects (meters for length, radians/degrees for
angles) with min/max bounds and model-readable errors. Makes LLM tool calls
safer — "6 in" no longer has to be pre-converted to 0.1524, and a bad value is
rejected with a message the model can self-correct from.
A shared `measurement(kind, unit)` zod field wraps lingo's parseQuantity as a
`number | string` union+transform; numbers pass through unchanged (backward
compatible), so no handler changes were needed. Covers create_wall, cut_opening,
place_item, create_level, create_story_shell, create_roof,
create_stair_between_levels, create_room, add_door, add_window, photo_to_scene.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a5963560b1 |
feat(editor): natural-language measurement inputs via @pascal-app/lingo
Type any unit into SliderControl / MetricControl fields — "6ft", "180cm", "1m80", "5'11\"", "72in", "45°", "1.57rad" — and it canonicalizes to the unit the field already stores (meters / degrees / radians / inches), independent of the metric/imperial display toggle. A live "= 1.83 m" hint previews the parsed value while typing. Only the typed-text commit path changes: drag-scrub, wheel, and arrow-key editing are untouched, and any unit the adapter doesn't recognise (%, unitless, rad/s) falls back to Number.parseFloat, so nothing regresses. The shared lib/measurement-parser.ts maps each field's `unit` prop to a lingo kind + canonical unit, covering the auto-inspector and every custom panel at once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
51fddc2d9c |
fix: capture-mode polish for studio & presets + pausable render loop (#469)
* fix(editor): keep snapshot pitch out of preset thumbnail capture The capture overlay's snapshot hint and 'Take snapshot' shutter label rendered in every capture variant, clashing with the save-as-preset flow's own 'Frame your item — click Capture' banner. Gate them on the existing isPreset discriminator and label the shutter 'Capture' there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): renderPaused flag suspends the frame loop Hosts that fully cover the canvas (e.g. the studio gallery overlay) can set renderPaused to stop FrameLimiter's RAF loop — a heavy animated scene otherwise keeps starving the GPU behind the overlay and makes it stutter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): drop the crop/format HUD in preset capture The pills crowded the save-as-preset banner and repeat what the fixed square frame already shows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): no contextual shortcut hints in the studio workspace Holding Shift surfaced select-mode/snapping hints over the studio compose panel and gallery, where there is no scene selection or tool to act on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |