adc1ec89f439d5464d114eddd62f5912e6a40892
134
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf89b5bcf2 |
feat: vertical building model — stored level heights, wall inversion, decks (#526)
* feat(core): stored storey heights groundwork — pure slab-support module + level height schema Extract pointInPolygon/computeWallSlabSupport and friends into a cycle-free packages/core/src/systems/slab/slab-support.ts (severs level-height -> spatial-grid-manager -> use-scene), add deriveLegacyLevelHeight as the pure mesh-free equivalent of the viewer's stacked level height, and add the optional LevelNode.height field plus the storey service (getStoredLevelHeight, getLevelElevations per-building prefix sums). No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): storey height badge + edit popover on level rows Each floating-level-selector row shows its storey height; clicking opens a popover with 2.5/3.0/3.5 presets and a free slider writing level.height. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): vertical-model load migration — stored heights, ordinal compaction, wall-top classification Pass 3 in migrateNodes: derive and store each legacy level's exact stacked height (never snapped), compact ordinals per building anchored at zero so basements stay basements, classify wall tops against the derived plane (|plane - top| < 0.20 strictly -> plane-bound, else explicit height materialized), and drop the blind totalRise 2.5 stair default on legacy scenes only. Epsilon and strictness validated by a prod census. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: pin wall tops to the storey plane; stored heights become the only vertical truth Wall-top inversion: a wall without an explicit height now tops out at its storey plane (resolveWallTop); slabs lift only the base. Window/door caps resolve the real top through the same slab election instead of Infinity. All level stacking (viewer, elevator, first-person, stair openings, MCP scene queries) reads stored LevelNode.height; the four divergent live derivations and level.metadata.height are deleted. Stair totalRise becomes optional and derives from the storey height when absent. MCP create-level stops writing its elevation param into the ordinal. Level creation sites write explicit heights; templates carry their true derived heights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp slabs/ceilings under the storey plane; sweep wall-height fallbacks; wall Top control Slab elevation writes clamp to plane − MIN_WALL_HEIGHT when plane-bound walls elect the slab (pure clampSlabElevationForWalls + registry handle bounds + shared panels for 2D/3D parity); ceiling heights clamp under the plane and auto-ceilings derive from resolved wall tops. Every remaining wall.height ?? 2.5 fallback resolves through resolveWallTop / resolveWallEffectiveHeight (panels, overlays, measurements, quantities, spatial grid, MCP reports); template walls matching their storey become plane-bound. Wall panel gains a Top control (Follows storey / Custom height) derived purely from height presence; the store update path now deletes keys passed as explicit undefined so plane-binding round-trips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): persisted support hosts — schema, host-preferring election, rendered-polygon unification Floor-placed nodes and walls gain a nullable supportSlabId. Elections surface the winning slab (getSlabSupportForItem, candidates query) and prefer a still-valid persisted host, falling back silently when the host is gone or reshaped away; deleting the host strips references in the same undo commit. Item-side support now tests the rendered slab polygon (like walls) through a per-level cache invalidated by the spatial-grid sync. Also adds the resolveStairTotalRise unit tests from the stage-1 gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: persist support hosts at commit; thread wall host preference everywhere Floor-placed commits (draft pipeline, per-kind creation tools, registry move tool) and wall create/move/endpoint commits persist supportSlabId via shared resolveSupportSlabPatch helpers — only when overlapping supports disagree on elevation, clearing it otherwise or when the node leaves the floor. WallSlabSupport surfaces electedSlabId; every wall support read site passes wall.supportSlabId as the preferred host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: split slab into placement + thickness; pools become explicit recess intent slab.elevation stays the walking surface; new thickness grows downward so the solid occupies [elevation − thickness, elevation]. Migration writes thickness := elevation for solids (byte-identical intervals, including degenerate zero) and recessed: true for legacy negative pools. Geometry branches on recessed instead of the elevation sign; presets keep today's intervals; free elevation edits move the body without coupling thickness (the deck semantic). Dead viewer SlabSystem component deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp ceilings to covering-slab undersides across levels getLevelAbove + getCoveringSlabUndersideAt give the first cross-level query; ceiling writes clamp to min(storey plane, lowest covering underside) − 0.01, and the space-detection reconcile now clamps manual ceilings down (never up) when a deck above intrudes — a flush deck reactively lowers the ceiling below it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: mezzanine and balcony build-tab tools One-gesture composites over the kernel: draw a deck footprint and commit deck slab + railings + stair (mezzanine) or deck + railings (balcony) in a single undo step. Fences gain supportSlabId and lift onto their host deck; railing runs split around the stair mouth; edges near wall centerlines are treated as closed. Stairs target the deck via explicit totalRise with no level-to-level opening sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: adaptive slab vertical editing; level vocabulary in UI copy Dragging a grounded slab's top stretches it (elevation and thickness move together — gaps impossible); floating decks move with thickness preserved and land grounded at zero; pools keep the drag-through-zero gesture. User-facing copy says level, not storey (Follows level, Level height). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp plane-bound wall tops to covering-slab undersides; fix vertical reactivity getWallPlaneTop samples the wall span against the level above's slabs, so a thick or flush upper floor shortens the walls below it instead of colliding (automatic attach, no dialog). Level-height edits now dirty the level's walls, stairs, ceilings, and fences; covering-slab changes dirty the level below. The effective-height helper triplicated across editor overlays moved to core. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: ceilings follow the level top by default ceiling.height becomes optional — absent means the ceiling tracks min(level top, covering slab underside) − 0.01 live, so level-height edits no longer require ceiling fixes. Ceiling panel gains the same Follows level / Custom height control as walls; auto-from-walls ceilings are created height-less and their height-derivation machinery is deleted; migration drops stored heights within 0.20 of the bound on legacy scenes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: wall plane clamp missed max-side boundary walls Auto slabs derive their polygon from wall centerlines, so covering-clamp samples sat exactly on the boundary where ray-cast point-in-polygon is side-dependent (min edges in, max edges out) — walls clamped or not by orientation. getWallPlaneTop now clips the wall's thickness band against the covering polygon (boundary-inclusive, arc-aware) and the ceiling bound's point sampling gained an explicit on-boundary test. Verified against the reported repro scene. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: stairs attach to decks; pointer-decided placement surface Stairs gain deckSlabId — rise follows the deck's elevation live (straight flights re-converge via a write-sync mirroring auto-openings), the panel shows a unified To destination with Follows deck / Custom rise, and the mezzanine tool attaches instead of baking a stale rise. Item placement under an elevated deck no longer flickers: grid events fed a feedback loop (the grid plane rode the ghost's elected height), so the support election is now capped at the surface the pointer ray actually hits, with a ground sentinel keeping under-deck commits deterministic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove the mezzanine/balcony composite tools Decks ship as catalog presets instead; the kernel the presets rely on (fence deck-hosting, stair deck attachment, clone remaps, pointer-decided placement) all stays. The tool code lives at e30042db for reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: gate wall adoption to grounded slabs; panel moves, drag stretches then unsticks Floating decks keep their drawn polygon (and stop being seam candidates for grounded neighbors) instead of growing into nearby walls. Panel elevation edits are pure placement; the viewport drag stretches a grounded slab up to 0.4 m then unsticks it into a thin deck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: deck-attached stairs land flush with the deck surface The rise now subtracts the stair's own elected base (same election the visual lift uses), so base + rise always equals the deck walking surface; the auto-sync defers a microtask so it reads a settled spatial grid and re-converges on both deck and base-slab moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: draw walls and fences on elevated decks Wall and fence draw tools now publish the pointed surface, so the draw plane rides the deck top (no more perspective-skewed floor hits) and previews sit on the deck. Fences gain real support election: a pure resolveFenceSupportSlabPatch persists the deck host at draw and reshape commits; wall commits thread the pointer cap so aiming under a deck elects the floor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: unit-aware height presets Level, ceiling, and slab preset buttons show clean values per display system (8/9/10 ft storeys, 8'-9' ceilings, whole-inch slab steps) instead of converted metric labels; metric presets unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: stairs converge to their resolved rise; deck attachment disables the cutout syncStairRises now converges every follows-mode straight stair (level or deck) plus deck-attached custom rises — detaching a stair from a deck re-derives its height, and ordinary stairs finally track level-height changes. Attaching via the panel writes slabOpeningMode none and hides the cutout controls; detaching restores the destination cutout and clears the stale explicit rise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: stacked-slab move hopping — one ray, one surface, one XZ The hop was hysteresis: the election consumed the riding grid plane's perspective-skewed hit, giving two self-consistent fixed points for one pointer ray. getPointedSupportSurface now returns the ray's crossing of the pointed surface and both the support cap and the cursor XZ derive from that single computation, so items stay on the surface the pointer aims at and sit exactly under the cursor across storeys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: single stair per click; tools restore select mode on exit The stair tool subscribed to both node clicks (synthesized on pointerup) and the native-click grid event with none of the guards sibling tools carry — one physical click over any node surface committed twice. A commit gate + follow-up click swallow fix the double dispatch, and the stair and column tools now restore select mode on exit instead of leaving the dead build-mode-without-tool state that ignored every click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: compact multi-selection panel with host footer slot Selecting multiple nodes now docks the collapsed-by-default panel on the right: N selected header, kind breakdown, and Duplicate/Delete mirroring the floating pill. A new multiSelectionFooter slot lets the host app dock actions below it, exactly like inspectorFooter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
494150dc1f |
fix(viewer): stack levels per building (#519)
* fix(viewer): stack levels per building * fix(viewer): recover legacy level ownership |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
f6ffb93a39 |
fix(export): unbreak bakes and STL/OBJ exports on malformed/placeholder meshes (#466)
- roof-system: replace BoxGeometry placeholders (initial swap + empty-roof merged shell) with a group-less degenerate geometry — a Box's 6 groups against the roof's 4-material array crashed GLTFExporter on every scene with a segment-less roof node (prod: 'reading isShaderMaterial'), and count-0 groups crash MeshBVH's packed-tree build. - glb-export: sanitizeMaterialGroups pass repairs any mesh whose geometry groups don't line up with its material array before GLTFExporter runs. - export-manager: give neutralised (attribute-less) meshes an empty position attribute before STL/OBJ export — both exporters read position.count unconditionally. - bake-exporter: log the full error stack so the bake worker's console relay captures it in the job's error trail. - viewer: host-controlled disablePostFx prop (Viewer → PostProcessing) that skips building the SSGI/denoise pipeline entirely; the ?disable=postFx URL flag previously only bypassed it per-frame while still allocating it. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bf25af6add |
viewer: Fix Dutch roof trim artifacts (#452)
* 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 spiral stair openings and fence handle arrows * Implement roof trim planes and ridge vent clipping * Fix mansard roof and ridge vent placement * Fix mansard merged roof cutouts * Fix Dutch roof gable overhang * Refactor roof segment, ridge vent, and surface geometry Remove Dutch ridge axis abstraction and rework roof edit system, ridge vent clipping geometry, and roof surface placement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Simplify Dutch roof shape * Add Dutch roof gable top geometry controls * Fix Dutch roof slope material slots * Render dutch roof tops as double-sided faces * Add auto ridge vent toggle to roof segments Track ridge vent auto-generation via an `autoRidgeVent` metadata flag so geometry changes only regenerate default vents when enabled, treating legacy segments with generated vents as auto-enabled for back-compat. Expose a panel toggle to opt in/out per segment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Snap new walls to the floor below Feed the walls of the level directly beneath the active one into the draft snap pipeline as extra references, so a new wall can align with the floor below. They share the same local XZ origin, and the list is kept separate from the current-level walls so the measurement HUD and wall splitting only act on the active level. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Set Dutch roof shape defaults on type switch Seed the Dutch shape parameters (waist width/height/length, top rake thickness/length) with sensible defaults whenever a segment is created as or switched to Dutch, so the gablet is well-formed regardless of leftover values from the previous roof type. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Use green accent for corner and endpoint snap markers Color the corner/endpoint snap markers and the vertical cursor pillar green across the 2D floorplan beacon, the 3D alignment guide dots, and the wall snap beacon so snap targets read as a consistent accent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add magnetic wall snapping to the roof tool Snap roof draft corners onto wall corners, midpoints, crossings, and bodies on the active level and the floor below, reusing the wall tool's snap pipeline so the beacon and coloring match. The cursor's ground dot/ring is hidden while a wall snap is active to avoid overlapping the beacon glyph. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update auto-generated Next.js route types path Regenerated next-env.d.ts now references ./.next/dev/types/routes.d.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Show cutaway outline while dragging roof trim Slice an untrimmed segment volume generated from the live node instead of the registry mesh, whose CSG rebuild lags a few frames behind the drag and may still hold placeholder geometry — so the section outline now renders deterministically. Use LineBasicNodeMaterial so the outline draws under the WebGPU pipeline, and export generateRoofSegmentGeometry for the slice source. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fill and clip roof trim cutaway, gate it to active drag Add a violet silhouette fill behind the cutaway outline, extend the section slicing to angled diagonal/corner trims via a generic vertical cut plane, and clip each slice to its footprint span so the infinite plane no longer sprouts stray lines across the rest of the roof. The cutaway now renders only while a trim handle is being dragged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Separate and extend Dutch roof end slopes Pull the Dutch hip end slopes out of the watertight shingle shell into their own slab wedge so they can be reshaped independently, and extend each end slope inward up its own hip plane until the top edge meets the gablet's inner triangle. Refactor roof-segment shape geometry into a shared roof-segment-shape module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Render roof trim cutaway as a material-only section cut Replace the triangle-mesh slicer with a CSG intersection of a thin slab against the untrimmed roof shell, so the cutaway shows red only on real material (wall + deck bands) and leaves the hollow attic empty. Add an analytic surface-edge outline, style both solid red like a SketchUp section, and make the cutaway persist whenever a segment is trimmed. Keep the merged roof shell visible during trim editing (re-trimmed live from each segment's drag override) instead of swapping in the per-segment meshes, whose abutting end-cap faces showed as stray white planes the commit never had. Extend each slab past free cut-line ends only — trimmed ends clamp to the cut line — so the red section stays inside the trim box. Re-export INTERSECTION from the viewer CSG surface for the editor. * Outline roof cutaway by fill silhouette, restyle to destructive red Derive the section-cut outline from the fill geometry's edges (EdgesGeometry) so it traces the real cut shape — wall/deck band boundaries and the hollow-attic edge — instead of just the top surface line. Drop the fill to 85% opacity and recolor both fill and outline to the app's destructive red, matching the delete/destructive UI. * Include roof accessories in trim clipping and red cutaway Roof accessories (chimney, vents, skylight, dormer, gutter, downspout, solar-panel, cupola) now slice at the trim plane like the roof shell and appear in the red section-cut while dragging a trim handle: - Export clipGeometryBySegmentTrim from the viewer as a reusable segment-local trim-clip primitive. - Add a shared useSegmentTrimClippedGeometry hook + TrimClippedMesh wrapper (nodes) that slice accessory geometry by the host segment's live trim override, so the cut tracks the drag. - Wire the clip into all 11 accessory renderers, including skylight glass panes and dormer window glass/frame/sill. - Feed every hosted accessory mesh into the editor's red cutaway, welding triangle-soup geometry (e.g. ridge vent) so CSG INTERSECTION yields a cross-section. - Register skylight in the scene-graph tree-node map so it shows in the outliner when placed on a roof. Co-Authored-By: Claude <noreply@anthropic.com> * Add smooth spline fences with editable curve handles Fences can now be drawn as one continuous Catmull-Rom/Bezier curve via an optional `path` (+ per-point `tangents`), selectable in a Straight/Curved mode toggle. Selected spline fences expose draggable control-point dots (hexagon) and symmetric tangent handles (circle) joined by a violet line, editable in both 2D plan and 3D. Side-move arrows are dropped for splines. Co-Authored-By: Claude <noreply@anthropic.com> * Fix dutch roof ridge vent handling * Fix Dutch ridge vent placement and support * Fix Dutch roof trim artifacts * Fix Dutch roof trim preview geometry * Tag roof trim overlay meshes with EDITOR_LAYER Child meshes relied on a parent group's layer, which three.js does not propagate, so the trim section/rail/plane overlays rendered on the scene layer — getting inked/SSGI-darkened and leaking into thumbnail exports. Co-Authored-By: Claude <noreply@anthropic.com> * Apply Biome cleanup * fix(core): address Dutch roof review feedback * chore: apply biome check cleanup * fix(core): relax Dutch roof surface helper input * fix * Fix biome checks and dev verification * fixes * Remove unsupported Biome noShadow override * Improve roof interactions and fence editing * Fix fence drag and ridge vent default handling * editor: drop wall-snap debug log, gate curved-fence finish hint on draft start Remove the leftover TEMP DIAGNOSTIC console.log in the wall tool's onMove hot path. Curved fences commit on a closing gesture (double-click / Enter) rather than per-click, so surface a 'Finish curve' hint in the fence HUD — but only once a point has been placed and a curve is actually in flight. The draft point count is published from SplineFenceDraft into a small ephemeral editor store (useFenceCurveDraft) that the contextual helper reads, mirroring the existing useSegmentDraftChain pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wassim SAMAD <wass08@gmail.com> |
||
|
|
db35af9a05 | Merge remote-tracking branch 'origin/main' into fix/bake-door-window-anims | ||
|
|
43fa2b1253 |
Merge origin/main into feat/placement-interaction-overhaul
Resolve 7 conflicts keeping our snapping migration + floorplan perf work as source of truth, combined with main's MEP run-continuation / Alt-detach / latch handles. Rebuilt two import blocks the auto-merge silently truncated (node-arrow-handles.tsx, duct-fitting/move-tool.tsx). Verified: tsc clean across core/viewer/editor/nodes/mcp, 451 tests pass, biome clean. Floorplan view-transform re-render storm confirmed pre-existing (not introduced by this merge). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7530de5348 |
fix(viewer): bake folding/garage doors at an un-flipped rest pose
`poseDoorMovingParts` assigned a single euler axis (`group.rotation.y` / `.x`). The live system was fine because the group's euler stays a clean (0, y, 0). But the GLB exporter clones the door and decomposes its matrix, which re-derives a gimbal-flipped euler (x=z=π) for any rotation beyond ±90° — folding panels reach ~158°. The reset to t=0 then only zeroed `.y`, leaving the π residue on x/z and baking a 180°-flipped rest pose (panels folded out toward a wrong position even when closed). Set the full euler triple via `.set()` in every pose branch so the other two axes are always zeroed, clearing any decomposed residue. Add a regression test that exports an open folding door and asserts an identity rest pose for all panels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4c81435e3b |
fix(doors): correct folding fold direction + make open-clip names unique per node
Two issues surfaced testing the baked viewer:
- Folding door folded toward +z (into the room) — the joint rotation sign
was inverted, so the accordion opened the wrong way ("weird position").
Flip to `(prevDirection - direction) * foldAngle` so leaves fold toward
−z, matching the original inline rig. Verified panel-for-panel against the
original formula at every operationState.
- Openable clips were named by display name (`<name>: open`), but the baked
viewer drives playback by clip name (`useAnimations` maps name → action).
Several windows share the name "Window 1", so their clips collapsed to one
action and triggering any one opened the first. Key the clip name by node
id (`<id>: open`) — unique, matching the item-loop convention; the
human-readable name still lives in `extras.label`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c629171607 |
feat(doors): bake open-animation clips for sliding/garage/folding/pocket/barn doors
Only swing doors (hinged/double/french) baked an open clip into the GLB — they carry a `pascalSwingLeaf` marker the exporter reads. Every operation door type (sliding, pocket, barn, folding, garage-sectional/rollup/tiltup) baked its `operationState` straight into mesh vertex positions at build time, so the exporter had no re-poseable node to sample and the artifact never flagged them `openable`. Give operation doors the same build-once + pose-at-t split windows already use. Each builder now emits its moving parts in a named group at the CLOSED pose, and `poseDoorMovingParts` (the single source of truth, shared by the live system and the GLB exporter) drives the open motion: - sliding/pocket/barn: rigid leaf translation - garage-tiltup: rigid hinge about the lintel - folding: hinged accordion chain (nested groups, per-joint fold) - garage-sectional: per-panel groups posed along the overhead curve - garage-rollup: the one type whose live geometry changes (slats roll onto a drum, which a glTF clip can't express) keeps its full-detail live rebuild; the curtain is wrapped in a top-pivoted group the exporter scales up into the lintel as the baked approximation. The exporter samples each operation door's motion into keyframe tracks (16 segments) so the non-linear rigs (curve, accordion) stay faithful, and stamps `extras.openable` + `extras.clips` so any glTF consumer can play it. Tests: per-type kinematics (groups build, rest closed, open) + sliding/roll-up clip baking (sampled position/scale tracks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
708fa5278e |
Merge remote-tracking branch 'origin/main' into feat/baked-glb-export
# Conflicts: # packages/editor/src/components/editor/export-manager.tsx |
||
|
|
d80ffd67d5 |
feat(viewer): re-light + re-control baked GLBs from the scene graph
Add a GLB interactive layer (`GlbInteractive`) that re-creates the item interactivity the parametric viewer has — point lights and the controls overlay — on top of a baked artifact. Effects + controls come from the DB scene graph (joined to baked nodes by `pascalId`, no sidecar); world transforms come from the baked Object3Ds. Lights are portaled into their item node so they ride level stacking, and intensity tracks the shared `useInteractive` store so overlay dimming works. Baked scenes load "lit" (toggles default on) for a showcase viewer feel. Extract `ControlWidget` into its own module so the parametric `InteractiveSystem` and the GLB overlay render identical controls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f773e6b8c5 |
feat(editor): placement & interaction overhaul — FSM spine, bug tracks, perf
Implements plans/editor-placement-interaction-overhaul.md: an authoritative interaction-scope state machine plus the catalogued placement/interaction fixes, and split-view floor-plan performance. - Interaction-scope spine (lib/interaction/* + store/use-interaction-scope), driven from central useEditor setters; overlay scoping (zone labels, context badges, floating action menu) reads resolveOverlayPolicy. - Bug tracks A/B/D/E/F/G/H: handle/cutout raycast, footprint validity, auto-slab loop, ceiling hosting, B-key tool desync, 2D drop offset, per-frame jank. - Snapping modes (grid/lines/angles/off) + contextual HUD chips; modifier model (Shift=cycle, Alt=free place, Ctrl=grid step). - Item move now tracks the cursor 1:1 (was a laggy per-frame lerp); handle rig hides during a whole-node move; rotate gizmo advertises Shift=free rotation in the HUD and hides the move cross while rotating. - Floor-plan perf: pause live reactivity while in 3D-only view; per-node geometry cache so only changed nodes rebuild on a drag; hoist wall miters to a once-per-pass ctx.levelData (O(N^2) -> O(N) on wall/opening drags). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a71de82ccb |
nodes: add MEP movement controls and DWV parity (#438)
* 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 * feat(duct): ceiling-snap drawing + connected-joint endpoint move Duct draw tool's ceiling mode now hangs each path point just below the ceiling actually covering it (per-room heights tracked), with a translucent surface highlight and a plumb line to the floor so the in-flight point reads clearly from any angle. Dragging a duct corner that sits on a fitting now carries the fitting's other ducts along (port-connectivity second hop), so the joint moves together instead of tearing apart. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): detach + vertical modifiers for duct/pipe joint editing Alt detaches a dragged duct/pipe endpoint or fitting from its connected joint (no elbow re-aim, no connectivity follow); Ctrl/Cmd drives vertical riser movement on the fitting move. Behavioral parity across 2D and 3D. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): full DWV pipe parity for joint editing Bring pipe-segment endpoint drags and pipe-fitting moves to parity with duct: free-drag endpoints, Alt-detach, Ctrl/Cmd-vertical riser, elbow re-aim, and connectivity follow. Generalizes the shared elbow-reaim and auto-fitting helpers to dispatch by run kind so 2D and 3D share one path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): wall-style arrow handles for duct fittings + segments Add violet directional arrow affordances to duct-fitting selection (height, move cross, rotate arc) mirroring the duct-segment rig: portaled into the parent frame to stay out of the selection outline, rendered via the shared HandleArrow, and carrying mated-run connectivity through the single-undo dance. The move cross engages press-drag-release (placementDragMode) the same way the floating drag does, so the markup hit-areas go inert and the fitting move tool commits on pointer-up. Also re-export the HandleArrow primitives from @pascal-app/editor and extend the duct-segment side-move/floorplan affordances. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): click-to-latch cube handles for duct + fitting editing Replace the hover-reveal / multi-handle selection rigs with a single click-to-latch cube that opens a directional cluster, shared between duct segments and fittings via a new selection-handles module (HandleCube / MoveChevron / RotateArc, all sized to the roof pitch cube). - Duct segment: per-vertex + run-center cubes reveal axis-locked move chevrons (down arrow always shown), plus a roll arc at the run center. - Duct fitting: center cube reveals six ±XYZ move arrows and three per-axis rotation arcs (oriented in place), replacing the old height/move/rotate trio with axis-cycling. - Rotation (fitting arcs + duct roll) snaps to 45° steps; Shift = smooth. - thin chevron profile + press-drag-release commit retained. * fix(mep): orient duct roll arc consistently + drop Ctrl-vertical drag Build a fully-determined basis for the duct roll gizmo so the curved arrow always seats at the top-outer 45° corner regardless of run direction, instead of an arbitrary apex from a single setFromUnitVectors. The selection-rig ±Y arrows now own vertical movement, so the redundant Ctrl-modifier riser drag is removed from the fitting move tool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): run-aligned duct handles, swing snapping, elbow flatten Align the duct run-center cube + horizontal arrows to the run axis (matching the per-vertex handles) while keeping whole-run translate. Endpoint side / up-down swing arrows now follow grid snap points and port-snap onto nearby collars (Shift sweeps smoothly). Relax elbow realign + fitting schemas to flatten to a straight 0° coupling. Surface HVAC-specific hints in the select-mode helper panel for duct / fitting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): per-segment linesets/liquid-lines with joint-follow editing Linesets and liquid lines now commit one independent two-point node per drawn segment instead of folding into a single mitered polyline, so each line selects and deletes on its own. Endpoint caps fill shared-coordinate joints so connected segments still read as continuous pipe. Dragging a shared endpoint carries mated segments along via port connectivity (Alt detaches), so a run still edits as one welded piece. Liquid-line follow mode traces the whole connected lineset run, laying a per-segment parallel line down its full assembled length. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): vertical-offset auto-routing on duct center-cube ±Y drag Lifting/lowering a connected run with the run-center cube now keeps each connected end welded to its stationary partner instead of dragging the whole network. Run-to-run ends get the classic S/Z offset (two elbows + plumb riser, partner trimmed back one leg); elbow-connected ends form a clean L — the existing elbow stays put and re-aims its collar vertical, with one new top elbow + riser reconnecting to the lifted endpoint. The offset is ghosted live and minted as a single undo step on release. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add roof accessory placement guides Measure roof accessory placement against the active roof face using visible surface bounds and preview geometry footprints. Add dormer-local guides and special linear handling for ridge vents and gutters. * Improve duct and placement routing * Fix duct vertical movement routing * Fix duct vertical offsets and roof accessory movement * Add DWV movement parity and line endpoint controls * Fix MEP handle review issues * Fix chimney placement and duct offset cleanup * Use snapped targets for roof accessory commits * fix(nodes): repair MEP movement review issues - auto-fitting: tee branch now follows the drawn lateral angle; update the stale square-tee test + doc comment that contradicted the rewrite - duct-segment: re-enable the vertical auto-offset rewind (the disabled stub left mintedIds empty, so re-dragging a tagged duct stranded old elbows/risers and stacked duplicates); remove the dead stub - duct-segment: strip the stale auto-offset tag on manual corner/roll commits so the horizontal-move path no longer trusts an out-of-date base - chimney: resume history before mutating segment children arrays so a cross-segment move reparents in one tracked transaction (undo stays consistent) - dormer: align schema test with the new windowSill=false default * fix: address mep movement review issues * fix: address follow-up mep review comments * fix: address additional mep review comments --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: pascal-open-bot <open@pascal.app> |
||
|
|
06e4cd7748 |
feat(export): baked GLB export with identity, clips, cutout fix (phases 0-1)
Promote the client GLB export into the baked-artifact format from
plans/editor-baked-glb-export.md (phases 0 and 1).
- NodeMaterial -> classic MeshStandardMaterial conversion at export. The
viewer's MeshStandard/LambertNodeMaterial set isNodeMaterial, not
isMeshStandardMaterial, so GLTFExporter would otherwise drop every
surface to a blank default. KTX2 (compressed) maps are decompressed via
WebGPUTextureUtils so the exporter can embed them (PNG for now; KTX2
re-encode is deferred to the phase-3 bake worker).
- Identity stamping from sceneRegistry: node.name = pascalId and
extras = { pascalId, kind, label?, openable?, clips? }; all other
userData stripped so editor/runtime ephemera never reach glTF extras.
- Door/window open clips baked from the build-once + pose-at-t primitives
(door pascalSwingLeaf marker, window poseWindowMovingParts). Clips named
by label ("Door 1: open"), carry extras.loop = false (consumers play
once and hold; dumb glTF players still loop).
- Cutout fix: door/window selection hitboxes hide via material.visible,
which onlyVisible misses, so the hitbox box plugged the wall opening.
Non-renderable container meshes now keep their node but lose geometry;
childless ones are removed.
- Editor-overlay stripping mirrors the thumbnail capture: emit
thumbnail:before/after-capture so scene-layer affordances (handles,
ceiling/site brackets) self-hide, and drop anything off SCENE_LAYER
(gizmos, grid, zone fills).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8633886eef |
fix(selection): selected objects keep their texture (light glow, no wash)
Selecting a textured wall/slab/item replaced its surface with a flat purple wash. Root cause: the selection highlight clones the material and tints it, but `NodeMaterial.clone()` on the WebGPU backend drops the texture-map node assignments, so the clone rendered flat — and a strong albedo blend + emissive washed whatever was left. Fix: re-attach the maps from the source material after cloning (shared by reference) and drop the albedo tint, keeping only a gentle indigo emissive so the real material/texture stays readable with a soft "selected" glow. Applied to both highlight paths: - generic editor highlight (slabs/items) in selection-manager - wall path (walls are excluded from the generic one), built lazily + cached/self-healing so it survives the wall finish's async texture load Removes the now-dead eager wall `highlightedVisible`/`selection` profile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
346b5d8cbe |
feat(fence): add horizontal-board fence style with capped posts
Adds a `horizontal` fence style that builds composite-cladding panels — horizontal boards stacked between square intermediate posts — instead of the vertical pickets that slat/rail/privacy all produce today. - New `style: 'horizontal'` (core schema + inspector Style control). - Posts march along the whole span at `postSpacing`, each with a cap. - New `postCap` control (none/flat/pyramid, default pyramid); pyramid caps render as a 4-sided cone fence part. - New `slatGap` control for the board reveal — 0 collapses to one flush panel so the stacked-board seams don't read as lines. - `postCap` + `slatGap` are horizontal-only (visibleIf); postCap uses a labelled dropdown so its options aren't a context-free switch. - Lower the `postSpacing` inspector minimum from 0.2 to 0.05 m. - 2D floor plan reuses the solid-panel marker for horizontal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c4b6e74d65 |
Merge remote-tracking branch 'origin/main' into feat/paint-slots
# Conflicts: # packages/core/src/store/use-scene.ts # packages/editor/src/components/editor/index.tsx |
||
|
|
c6423e7c73 |
feat(paint-slots): migrate wall + procedural kinds onto node.slots
Retire the inline material fields on every slot-model kind, moving them
onto the unified node.slots model on load so painting, edit-propagation,
and the picker behave uniformly.
Walls: slots {interior,exterior}; slot-first viewer resolution threading
sceneMaterials (content folded into the wall material hash); WallRenderer
subscribes to the scene-material palette so a scene-material edit
re-renders live; wallPaint rebuilt on createSlotPaintCapability.
Load migration generalizes legacy -> slots across slab/ceiling (surface),
fence (posts/infill/base/rail), column (shaft/base/capital/frame), shelf
(shelves/frame/back), and stair (per-role tread/side/railing). Library/
scene refs pass through; inline customs mint a deduped scene material;
legacy fields cleared. No visual change (renderers already fell back to
the legacy fields). Roof/chimney/dormer/vents intentionally stay on their
role system.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
faf73d66f2 |
feat(paint-slots): full world-space UVs for roof gable walls (match walls)
The earlier gable fix world-referenced only V, leaving U in segment-local space — so gable and wall lined up vertically but slid horizontally. Replace the V-only scalar offset with the segment's full world matrix (roof group composed with segment transform) and project vertical faces exactly like the wall kind: U = ±worldX/Z, V = 1 - worldY. Gable now tiles continuously into the walls in both axes. Sloped shingle UVs are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
65a7fcb74b |
feat(paint-slots): world-space planar UVs for walls (seamless tiling)
Walls used THREE's ExtrudeGeometry UVs, which restart at each wall's local start/end — so finishes seamed at wall joins, showed a mid-face axis-switch stripe, and didn't line up with the roof gable. Re-project the render mesh's UVs in WORLD space (1 unit = 1 m), matching roof-system's pushRoofUv exactly: vertical faces U = ±worldX/Z, V = 1 - worldY. De-indexes so each triangle uses its own face normal (no edge seams). Applied only to the render mesh; collision and floorplan geometry keep the original UVs. Now walls tile continuously across segments and meet the gable seamlessly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c7d0cd3388 |
feat(paint-slots): world-reference roof gable wall UVs to align with walls
The gable wall band tiled its V in segment-local space, so its texture phase didn't line up with the wall below (whose ExtrudeGeometry UVs map V = 1 - height-from-base). Make vertical roof faces tile V in world space (V = 1 - worldY) via the segment's resolved world Y (parent roof + segment position), so the band matches a ground-floor wall's vertical tiling at the eave seam. U is unchanged (stays in the face's local run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0e8df1b946 |
fix(paint-slots): nudge fence vertical members 0.001m thinner than the rails
The posts/infill and the accent rail shared the same panelDepth*0.35 depth, so their faces were coplanar where they cross -> z-fighting. Make the verticals 0.001m shy so the rail wins the depth test cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f7e774485c |
fix(paint-slots): guard empty fence slot group before mergeGeometries
mergeGeometries throws on an empty array, so an absent slot group (infill with showInfill off, base on a floating fence) crashed the geometry build. Return an empty BufferGeometry for an empty group; the renderer already skips meshes with no position attribute. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bba0cc5f68 |
feat(paint-slots): prepared-drywall finish + fence 4 slots matching build options
- Add 'concrete-drywall' (Prepared Drywall) catalog finish + 512 KTX2/webp runtime assets; default walls (WALL_SLOT_DEFAULT interior+exterior) and the roof wall/trim band to it so roofs read continuous with walls. - fence: replace the 2-slot panel/rail split with 4 slots that match the panel's build options — posts / infill (showInfill) / base (grounded only) / rail — each its own mesh with userData.slotId; conditional slots track the build state. Defaults: posts/infill/base charcoal, rail wood-finewood27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e92ee05702 |
feat(paint-slots): round-2 feedback — fence 2 slots + UVs, roof defaults, stair body
- fence: split into two paint slots — panel (posts/base/infill, default charcoal) and rail (cap, default wood-finewood27) — as separate meshes with userData.slotId. Fix applyFenceUVs to continuous world-space 1 UV unit = 1 m (drop the per-part min origin that broke tiling across parts). New generateFenceSlotGeometries. - roof: real catalog defaults for the segment surfaces via getRoofMaterialArray (the actual default path): wall/trim concrete-plate (matches walls), deck + soffit soft-white, shingle terracotta; textures-off role escape hatch kept. Align nodes getRoofMaterials no-parent fallback to match. - stair: body slot default -> preset-lightgrey. - (biome formatting normalization of the round-1 merged renderer files rides along.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8aa179e9cb |
feat(paint-slots): per-part paint for windows + doors, chrome/brass, world-scale UVs
Builds on the explicit per-mesh slot tagging (currentDoorSlot/currentWindowSlot): - Per-part painting: door = panel/frame/glass/hardware, window = frame/glass, each independently paintable. The recessed door/window body sits behind the wall, so the proud invisible cutout wins the scene raycast over the wall and the shared resolveSlotByReRaycast() re-raycasts the kind's own subtree to pick the exact part under the cursor (panel↔frame↔glass↔hardware). Hover tracks the cursor via a re-eval (idempotent, no flicker). - Door frame is its own slot (separate frameMaterial); hardware = new flat 'metal-chrome'. - Library defaults (generic): panel/frame -> library:preset-softwhite, glass -> library:preset-glass (flipped preset-glass to FrontSide — DoubleSide poisons the WebGPU MRT pass; it's the only glass we use). - Catalog: add flat (non-PBR) 'metal-chrome' + 'metal-brass'; drop metal metalness 1 -> 0.6 so metals are lit by existing lights (no env needed). - World-scale UVs (1 unit = 1m) on door/window box meshes via shared box-uv.ts, so finishes tile at real-world scale instead of stretching. - PaintResolveArgs gains an optional for subtree re-raycasting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c3bd065f2f |
fix(paint-slots): disable the CSG cutout's raycast so window/door paint hits the slots
The real interceptor was the 'cutout' mesh, not the hitbox: it's a 1m-deep CSG helper whose front face sits 0.5m proud of the glass, and current three.js raycasts invisible meshes — so every paint/hover ray resolved to 'cutout' (no slotId → role null). Disable its raycast; combined with the hitbox noop, paint/hover rays now land on the tagged frame/glass (panel/glass) children. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cebb29460d |
fix(paint-slots): make window/door paint hits land on the tagged children
The window/door root is an invisible hitbox the system gives full-depth BoxGeometry; its front face intercepted every paint/hover ray, so the hit resolved to the hitbox (no slotId) → role null → paint silently disabled. Disable the hitbox's own raycast in the visual path so R3F's recursive intersect returns the tagged frame/glass (panel/glass) children instead; selection still works because those child hits bubble to the root's event handlers. Restored to the default raycast each build, and kept for 'opening' windows/doors (no visuals to paint, still need a selectable hitbox). The 'cutout' child is visible=false so the raycaster already skips it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9f1627e923 |
feat(paint-slots): paintable slots for windows + doors (frame/glass, panel/glass)
Windows and doors build all visuals in their viewer systems from module-global materials, so this threads per-node slot materials + userData.slotId tags through those builders without restructuring them: - window: 'frame' + 'glass' slots. door: 'panel' (body = casing + leaf) + 'glass'; the opening reveal keeps its own material. - Each system captures per-frame viewer state, then updateWindow/DoorMesh points the builder-facing base/glass materials at the node's resolved slot override (recomputed per node, so the next node resets without a restore). Meshes are auto-tagged in the shared addBox/addShape helpers by which material they got. - Textures-off still collapses to the role material (escape hatch); a slot override only applies in colored mode. - Editing a referenced scene material re-dirties the window/door (these systems aren't covered by GeometrySystem's scene-material re-dirty). - New paint capabilities (resolve role from userData.slotId, preview by userData.slotId) + capabilities.slots; window/door dropped from the paint disabled list. Shared previewSlotByUserData helper. Defaults unchanged: unpainted windows/doors render exactly as before (the slot fallback is the existing frame/glass material), so no visual regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
967a905e3b |
feat(paint-slots): unified slot defaults + paint for slab, ceiling, wall (phase 5)
Brings slab, ceiling, and wall onto the unified slot contract the shelf established, so each declares its paintable slots with a declarative default and (slab/ceiling) is painted through the registry capabilities.paint dispatch. - Shared helper packages/nodes/src/shared/slot-paint.ts: a node.slots-based PaintCapability factory (commit/resolve/effective generic; preview injected). Distinct from surface-paint.ts, which writes the legacy inline node.material. - slab: schema slots; def.geometry resolves node.slots.surface -> legacy material -> declared default, tags the mesh userData.slotId; slabPaint + capabilities.slots. Retires DEFAULT_SLAB_MATERIAL in the slab path. - ceiling: schema slots; material builders extracted to ceiling/materials.ts (shared by renderer + paint preview, built BackSide so the hover preview is visible from below); renderer resolves the slot; ceilingPaint + slots. - wall: WALL_SLOT_DEFAULT in core; the viewer's getMaterialsForWall renders an unpainted face with its declared default instead of the themed wall role; capabilities.slots (interior/exterior). wallPaint's inline interior/exterior fields are unchanged (node.slots migration is a later step). - selection-manager + material-paint: drop slab/ceiling from the legacy single-surface arms (now registry-driven). Behavior change (intended, matches the shelf precedent + the phase-5 plan): colored-mode UNPAINTED slab/ceiling/wall surfaces now render their fixed slot default (#e5e5e5 / #f5f5dc / #ffffff) instead of the theme role colour. The textures-off (monochrome) role collapse is unchanged — the escape hatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8eab9979ab |
fix: address 2nd-round adversarial review of PR #402 fixes
- core: decouple drag-follow from distributionRole — add portConnectivityFollow flag to NodeDefinition; pipe-trap opts out (portConnectivityFollow: false) so dragging a connected pipe endpoint stretches the trap arm instead of yanking the anchored trap fixture - core: remove the module-level getLevelHeight cache entirely — it was keyed only by nodes-object identity, which could return stale heights for in-place mutations by pure/headless callers. The function is now fully pure and deterministic; viewer hot path recomputes per frame as before (the cache only ever skipped the resolver-free branch) - test: harden port-connectivity-pipe.test.ts — real DuctSegmentNode cross-family isolation case (was waste-vs-vent), new pipe-trap anchor case (run drag doesn't move trap; trap drag still stretches run), and beforeEach/afterEach registry reset instead of leaky beforeAll - nit: biome format/import-order on all touched files |
||
|
|
54a24e4c5c |
fix: address review findings from PR #402 MEP systems
- core: fix layer violation in level-height.ts — extract sceneRegistry
import, replace with optional WallBaseYResolver callback so core stays
pure (no Three.js mesh state); viewer callers pass resolver, headless
callers (MCP/tests) get deterministic node-data-only result
- core: generalise port-connectivity service from duct-only to all
distribution families — match partners by distributionRole ('run' →
endpoint stretch, 'fitting' → rigid follow) instead of hard-coded
duct-segment/duct-fitting type names; add system-compat guard so
cross-system ports (e.g. supply duct vs waste pipe) don't fuse
- editor: fix port-snap rotation bug in move tool — pass preview node
at live rotation into resolvePortSnap so own-port positions reflect
any mid-drag R/T rotation before computing the snap delta
- editor: wire pipe-trap into UI — add to StructureTool union,
MepToolKind, MEP_ITEMS Build-tab tile, and structure-tools action menu
- test: add port-connectivity-pipe.test.ts — 2 tests covering
pipe-fitting → pipe-segment endpoint drag and cross-system isolation
- test: fix stale pipe-auto-fitting.test.ts wye expectation — author
deliberately chose square sanitary-tee for DWV side-taps (documented
in PR description and PipeFittingNode schema); update the one test
that still expected wye to match the implemented behaviour
- nit: fix optional-chain biome warning in validate-dwv.ts
|
||
|
|
5551500d98 |
feat: HVAC ductwork + DWV plumbing systems (#402)
Adds two new MEP node families (HVAC ductwork, DWV plumbing) built on a shared port-connectivity model. Co-authored by @sudhir9297. |
||
|
|
101341d98d |
feat(paint-slots): paintable slots on the procedural shelf (phase 5)
Proves the unified (nodeId, slotId) slot contract on a procedural generator, beyond items and walls. A shelf now exposes three paintable slots — shelves / frame / back — painted through the same PaintCapability dispatch and the same node.slots: Record<slotId, MaterialRef> shape items use. Foundation (shared, reusable by future procedural kinds): - core: SlotDeclaration type + capabilities.slots(node) registry declaration; GeometryContext gains `materials` so a pure builder can resolve scene:<id> slot refs without importing useScene. - viewer GeometrySystem: threads the scene material library into every builder ctx, and re-dirties (bypassing the geometryKey skip) any geometry node that references a scene material when that material changes — so editing a custom colour propagates to every shelf using it, matching items. Shelf: - schema: slots: Record<string, MaterialRef> (mirrors ItemNode). - geometry: per-slot material resolution (slot override -> legacy whole-shelf -> declared default colour); every mesh stamped with userData.slotId; DEFAULT_SHELF_MATERIAL retired (declared default gives identical off-white). - paint.ts: PaintCapability (resolveRole from userData.slotId, scene-material commit for one-off colours, preview restricted to __fromGeometry meshes so hosted items aren't ghosted, getEffectiveMaterial incl. legacy fallback). - definition: paint + slots capabilities; slots folded into geometryKey. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
937cf02aff |
feat(paint-slots): world-scale pool slab floor UVs + document the metre UV contract
Pool slabs (generatePoolGeometry) were the one procedural surface emitting normalized [0..1] floor UVs, so a textured finish stretched to fit the pool instead of tiling at real-world scale like every other surface. Switch the floor to shape-space metres (x, -z) — the same mapping generatePositiveSlab- Geometry already uses for its caps. Pool walls were already in metres. Also document the contract in wiki/architecture/materials-and-themes.md: every procedural surface generates UVs in metres (1 UV unit = 1 m), GLB slots follow the same ~1 unit/m authoring convention, and a catalog material's `repeat` is therefore a per-material world-scale setting (tiles per metre), identical for every surface that uses it — never per-item. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1628b728fa |
Always-visible placement ghosts + true-nearest 2D opening snap (#407)
* feat(editor): always-visible translucent placement ghost for openings + roof accessories When a host-surface placement tool is armed, the node's real geometry now follows the cursor everywhere as a translucent ghost: tinted invalid (red) and unconfirmable off-host, snapping onto its host surface (wall/roof) with the existing valid/invalid affordances when near. This replaces the old red wireframe box (door/window) and red DragBoundingBox (roof accessories), so the armed tool is visible before the cursor reaches a placeable surface. - New shared `applyGhost` helper (nodes/src/shared/ghost-materials.ts): clones materials, disables raycast (avoids cursor-ray starvation), tints invalid; cleanup disposes only the clones. - New door/window preview components built from the real geometry via new `buildDoorPreviewMesh`/`buildWindowPreviewMesh` viewer exports; tools float the ghost via a `fallbackPose` that is mutually exclusive with the on-host draft + wireframe outline. - `RoofAttachmentFallbackPreview` gains a `ghost` prop; all 11 roof-accessory tools pass their real preview (invalid-tinted) instead of a box `size`. Snapping behavior is unchanged (no proximity snap yet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): magnetic proximity wall-snap for door/window placement The door/window ghost now follows the cursor over the floor like a moving item and magnetically snaps onto the nearest wall within range (1.5 m), then releases back to free-follow when the cursor moves away — instead of only attaching on a direct wall-mesh ray hit. A grid-snap sound plays each time it snaps onto a new spot, so it reads as moving a physical object that can only land on walls. - Plan-space proximity via the existing `findClosestWallInPlan` (the same helper the 2D floor-plan move uses): level-scoped, skips curved walls, returns wall + along-wall localX + side + wall-local rotation. - `grid:move` drives the snap and `grid:click` commits when proximity-snapped; a direct wall-mesh hover (wall:enter/move) still owns the precise face side. Both paths share `applyWallTarget` (create the draft once, reparent only on an actual wall change) and a shared commit that refreshes alignment anchors. - Disambiguation without a stuck flag: a per-pointermove `timeStamp` gate (R3F + the grid raycast share the source DOM event) plus a `cameraDragging` guard and stale-`hostKind` reset, so a missed wall:leave during a camera orbit can't strand the draft. - Window keeps its sill height on the floor path (the floor cursor carries no wall-face Y) — defaults to a ~0.9 m sill, mirroring the 2D move. - Shift bypasses the along-wall grid/alignment snap but still attaches to the nearest wall, matching the 3D-hover and 2D-move conventions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): door/window preset placement follows the cursor over open floor The community preset/catalog flow places doors and windows through the isNew move path (MoveDoorTool / MoveWindowTool), which had no free-follow: the fresh clone was parented to the level at the origin and only became visible once the cursor reached a wall, so over empty floor nothing tracked the cursor. Now the move tools mirror the def.tool placement behaviour: - Off-wall, the real node rides the cursor like an item (reparented to the level, positioned at the building-local cursor) so it's obvious what's being placed before it attaches. - Within range of a wall it magnetically snaps on via findClosestWallInPlan (the same plan-space helper the 2D move uses), releasing back to free-follow when the cursor moves away, and plays the grid-snap sound on each new snap. - grid:click commits only when snapped (open floor is a no-op — a door/window needs a wall); the wall/roof mesh-hover paths are unchanged and still own their own click. A per-pointermove timeStamp gate + cameraDragging guard keep the floor handler from fighting a wall/roof hover. - Windows default to a ~0.9m sill while off-wall (fresh preset clones carry position [0,0,0], which buried half the window below the floor). The wall/roof commit body is extracted into a shared commitToWall so the mesh-click and proximity-click paths stay identical. Existing-node moves are fully restored on cancel/unmount (the node stays isTransient through free-follow). Standalone-editor def.tool placement already had this in a prior commit; this brings the community move path to parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): 2D floorplan ghost follows the cursor for door/window placement Mirrors the 3D free-follow in the top-down floor plan: while placing a door or window, a loose footprint rectangle now follows the cursor over open floor so it's obvious what's being placed before it snaps to a wall. The instant the cursor nears a wall, the existing synthesized wall:enter/move path takes over and the real on-wall door/window symbol (swing arc, etc.) replaces the ghost. - The opening-placement pointer-move handler in floorplan-panel sets a new `openingGhostPoint` on the off-wall (findClosestWallPoint miss) branch and clears it on a wall hit; a loose width × 0.1m rectangle renders at that point inside the floor-plan scene group (same world→SVG transform as every glyph). - Width comes from the moving node or the kind default (door 0.9 / window 1.5). - The ghost clears when opening placement ends (tool/mode change, cancel, commit) and on level change, so no stale rectangle lingers. Deliberately a plain rectangle, not the full swing-arc symbol: off-wall there's no host to orient the swing to. The shared door/window def.floorplan builders are untouched — overloading them with a wall-less fallback would make roof-hosted doors (parent is a roof segment, builder returns null today) draw stray rectangles in plan. Keeping the preview in the editor layer avoids that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): faithful 2D door/window ghost, 2D wall-slide, R-flip during placement; 3D snap only on hover Three placement fixes plus a snapping revision, for both the standalone def.tool path and the community isNew move path (door + window): - 2D faithful ghost: the off-wall placement ghost now renders the real blueprint symbol (door swing arc / window panes) following the cursor, not a bare rectangle. Done by publishing a transient opening on a synthetic wall to usePlacementPreview (extended with a `parentNode` fed as the builder's ctx.parent) so the real def.floorplan builder draws it. Cleared on wall-hit, on commit, on placement-inactive, and on level change. - 2D slide-along-wall: the floor-plan registry layer ignored useLiveTransforms for door/window (only floor-placed + slab/ceiling/zone), so a same-wall slide updated the 3D mesh but left the 2D symbol frozen. It now merges the wall-local live position/rotation onto the node (keeping parentId) so the 2D symbol slides with the cursor. - R-flip during placement: pressing R now flips a door/window's facing (front ↔ back, rotation += π) before commit — the placement tools own R while placing (the global selection-based R/T handler stands down via isPlacingOpening so it can't double-fire). No-op on roof faces (front-only). - 3D snapping zero-padding: removed the 1.5 m proximity magnet; in 3D the opening free-follows the cursor over open floor and snaps only when the cursor ray actually hovers a wall/roof mesh (big raycast targets). 2D keeps its 0.5 m findClosestWallPoint padding since plan walls are thin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): make FloorplanRegistryMoveOverlay the sole 2D owner of door/window placement Community door/window placement (movingNode + metadata.isNew) had TWO 2D paths running at once: the floorplan-panel synthesized wall:*/grid:* events (driving the 3D MoveDoorTool) AND FloorplanRegistryMoveOverlay via def.floorplanMoveTarget. They fought — R didn't flip the 2D symbol and clicks didn't commit in 2D, while 3D worked. The overlay + floorplanMoveTarget is the purpose-built 2D owner (faithful def.floorplan symbol, plan-space CTM coords, Figma snap, single-undo commit), so it now owns 2D opening placement when movingNode is set: - floorplan-panel: the opening pointer-move branch + the registry grid catch-all + the background-click catch-all all now exclude the door/window MOVE case (`!isOpeningMoveActive`), so the synthesized events no longer fire for it (they still drive pure raw-build placement, which has no movingNode). Without the catch-all exclusions the move case fell through to grid:move/grid:click, which re-drove the 3D tool's free-follow and consumed the commit click. - R-flip in 2D: `FloorplanMoveTargetSession` gains optional `flipSide()`; door/window floorplan-move implement it (XOR the wall-derived side + π rotation, re-running the last apply). The overlay's keydown calls `session.flipSide()` on R — gated on `hasMovedSinceStart` so it only fires when the 2D pane is the active mover (the 3D MoveDoorTool owns R in 3D/split; this prevents a double flip / double cue on one R press). - Commit in 2D now flows solely through the overlay's pointerup (no competing synthesized wall:click), so click-to-place commits. The global use-keyboard R/T already stands down during opening placement (isPlacingOpening), so a selected node can't also flip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): 2D door/window move free-follows the cursor off-wall and commits only on a wall Moving an existing door/window in the 2D floor plan: the move target's `apply` early-returned off-wall (`if (!hit) return`), so the opening stayed frozen on its old wall instead of following the cursor between walls (3D free-follows), and an off-wall confirm click committed the stale last-wall position — looking like the placement failed. Now `doorFloorplanMoveTarget`/`windowFloorplanMoveTarget` mirror the 3D move: - Off-wall, `apply` free-follows the cursor — hides the real node and floats the faithful door/window symbol at the cursor via a synthetic wall published to `usePlacementPreview` (the same preview layer fresh placement uses). The real node is `visible:false` so the registry layer skips it (no double symbol). - Back on a wall, it clears the ghost, reveals the real node, and snaps as before. - `canCommit` returns false while off-wall, so an open-floor click reverts to the pre-move snapshot (door returns to its wall) instead of committing in mid-air — matching the 3D move, where clicking open floor commits nothing. On a wall the commit lands normally. The overlay's snapshot revert (cancel / invalid commit) and the on-wall `apply`'s `visible:true` restore both guarantee the node is never left hidden after a move. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): 2D opening snap picks the true nearest wall, with a tighter radius The 2D door/window snap felt too aggressive and could grab a wall further away than the one the cursor was actually nearest. Root cause in `findClosestWallInPlan`: it compared a candidate's true segment distance against the previous best's `perpDistance` (signed offset to the wall's infinite line, not the clamped segment distance). Near a wall end those diverge, so a closer wall could be rejected / a farther one kept. - Track the best segment distance and keep the strict minimum — the wall chosen is now always the single closest segment to the cursor (true nearest), which resolves correctly when many walls sit close together. - Tighten the snap radius from 1.5 m to 0.4 m: plan walls are thin, so the old radius snapped from far away. The opening now free-follows the cursor until it's genuinely near a wall. Only the 2D move/placement targets use this helper (3D snaps on raycast hover); wall-attached items share the same improved nearest-wall behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): true-nearest 2D opening snap with dev hit-area overlay Round 7 of the placement-ghosts work. Make the 2D door/window wall snap always pick the wall nearest the cursor, fix fresh-placement snapping/slide in 2D, and add a dev-only overlay that visualises each wall's snap region. - Extract the plan-space nearest-wall-segment math to core (`lib/wall-distance.ts`: collectLevelWallSegments / closestOnSegment / nearestWallSegment / WALL_SNAP_DISTANCE_M). `findClosestWallInPlan` delegates to it, so the snap and the debug overlay share one source of truth. WallHit contract unchanged. - door/window 2D move now resolves the host level via the shared `getOpeningHostLevelId` (wall-hosted, roof-hosted, AND fresh-placement parented straight to the level — the last case previously resolved to the building, so a new opening never snapped in 2D). - Cursor resolver switched to absolute mode: query the snap with the true cursor, not the original-wall position + grab delta, so it picks the cursor-nearest wall (matching the 3D move) instead of a far wall across a thin gap. - 2D move clears any stale `useLiveTransforms` entry for the node each apply: the registry layer renders door/window from the live transform in preference to the scene node, so a leftover entry from the 3D tool froze the 2D slide for fresh / re-armed openings. - Fresh window defaults to a 0.9 m sill in 2D (was sitting half-below floor at y=0), matching the 3D MoveWindowTool. - New dev-only FloorplanVoronoiLayer + `show2dVoronoi` editor flag: draws each wall's snap hit area as an analytic capsule (no grid sampling), gated on a developer-menu toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cf24b62c44 |
fix(viewer): UI flicker on camera move from interactive overlays + dirty-mark leaks (#401)
* chore: sync bun.lock with 0.9.1 workspace versions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): stop interactive overlays from starving frames and flickering the UI Every interactive item mounted a drei <Html occlude> overlay unconditionally — invisible (opacity 0) when no zone was selected, but still alive. With `occlude` as a bare boolean, drei raycasts the entire scene per overlay on every camera-move frame, and rewrites each element's z-index while toggling display when the occlusion flips. On scenes with hundreds of interactive items (recessed lights, ceiling fans) this starved the frame budget and made the whole DOM UI blink during camera moves while the WebGPU canvas stayed healthy. Overlays now mount only while a zone is selected and the item sits inside its polygon, fade in/out over 300ms (the child components stay rendered so the exit transition can play before the <Html> unmounts), and drop `occlude` entirely. eps=-1 works around a drei mount bug: its mount path writes the element transform without the distanceFactor scale, and with a static camera the eps guard never re-applies it, so freshly mounted overlays stayed mis-scaled until the camera moved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): break down dirty nodes by kind in the perf overlay DIRTY now reads e.g. "29 (12 wall, 9 ceiling, 8 item)" — sorted by count, only non-zero kinds, with a "missing" bucket for dirty ids whose node no longer exists. Makes dirty-mark leaks attributable at a glance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(core): skip dirty marks for kinds with no dirty consumer dirtyNodes is consumed by GeometrySystem (def.geometry), FloorElevationSystem (capabilities.floorPlaced), and the legacy per-kind viewer systems. Site, building, level, zone, and guide match none of those, so their marks were never cleared: they accumulated for the whole session (every child create/delete dirties its parent), permanently defeated every consumer's empty-set early exit each frame, and polluted the perf overlay's DIRTY readout. NodeDefinition gains an explicit dirtyTracking?: boolean opt-out (default tracked — no derivable predicate exists since wall's dirty consumption lives in the viewer while zone/guide/level declare def.system for unrelated per-frame work). markDirty consults the registry; the five structural kinds opt out. Also fixes a second leak: deleteNodesAction never removed deleted ids from the dirty set, and every consumer skips missing nodes without clearing them, so marks on deleted nodes lived forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: replace concise-arrow forEach with for...of in deleteNodesAction biome's useIterableCallbackReturn rejects forEach callbacks that implicitly return a value (Set.add / clearDirty). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
aab48e053f |
fix(editor): harden editor interactions and WebGPU rendering
Fix editor bug sweep regressions, WebGPU CSG/material crashes, Shift snap bypass behavior, arrow handle drag projection, and the wall preview null guard covered by the Sentry follow-up PRs. |
||
|
|
aa3b0ef758 |
refactor: release-review cleanup for roof wall openings
Dual review pass (Claude multi-angle + Codex release-quality). One correctness fix and the agreed do-now cleanups: - fix: clone-scene-graph remaps roofSegmentId like wallId in both clone paths — duplicated scenes/levels kept pointing roof-hosted children at the original segments. - extract the settled, stateless roof target/cursor math shared by the four door/window tools into shared/roof-wall-opening-placement.ts (resolveRoofWallOpeningTarget + getRoofWallOpeningCursorPose + worldToSelectedBuildingLocal); tools keep the stateful lifecycle (drafts, undo/temporal, commit field lists). −199 net lines. - rename host-generic state: currentWallId→currentHostId, markWallDirty→markHostDirty (they hold segment ids too); capability cascadesViaHostSegment→dirtyHandledByOwnSystem (behavior-facing, before the public API hardens). - drop getRoofAccessoryKinds from core's public API — its only caller was the standalone Build tab, which now enumerates the registry inline with its app-specific filter. - window move-tool uses the shared stripPlacementMetadataFlags; stale "segment-local" comment fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b8dfa90762 | Merge remote-tracking branch 'origin/main' into feat/editor-ux-rendering-placement | ||
|
|
7879b83df3 |
feat: face-frame hosting for roof wall children + items on roof walls
Wall-mounted items join doors/windows on roof-segment wall faces, and the storage model moves to FACE-LOCAL coordinates so hosted children track segment edits live. - Children store roofFace + position [u, v, z-from-mid-plane] with rotation 0 in-frame — the exact wall-child conventions (the wall volume's mid-plane lands on the nominal footprint). A shared <RoofFaceHostFrame> derives segment pose + face frame from the live-override-merged segment: children follow resize handle drags in real time and never jump on commit. No re-anchor cascade needed — position is authoritative, the frame is derived. migrateNodes converts branch-era segment-local data. - Items: roofWallStrategy + roof:* handlers in the placement coordinator (surface 'roof-wall'), Shift free-place normalized with walls, ItemSystem wall-side push extended to segment hosts, correct 2D plan glyphs via face→segment→roof pose composition. The roof hit resolver + overlap guard moved to @pascal-app/editor (the coordinator lives there; nodes already depends on editor). - Cuts: subtractAccessoryCuts extracted and applied in BOTH the merged-shell and per-segment CSG paths (full edit mode / painted segments used to lose every hole), built from the CURRENT host geometry and live-effective children so holes follow segment and opening drags. - Handle rig: the grandparent portal now maps the node's world pose into the portal frame instead of composing parent+node registry poses — correct for any nesting (the face-frame group broke the old assumption), identical for walls. - Host-field hygiene: useDraftNode.commit/adopt and the window panel duplicate forward roofSegmentId/roofFace/wallId; every roof↔wall re-anchor clears and every revert restores them. Codex-reviewed (design consultation, adversarial rounds on the replaced cascade and on this refactor); frame conventions locked by unit tests. Record: private-editor plans/editor-roof-wall-openings.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bdfee058bd |
feat: doors and windows on roof-segment wall faces
Openings now host on the walls a roof segment generates — the base walls under the roof and the coplanar gable/shed/gambrel end faces, so a window can sit in a gable pediment. - core: roof-segment-walls.ts models the four vertical faces as 2D frames (u along face, v height) with convex profile polygons that mirror the wall volume getRoofSegmentBrushes builds; rect-in-profile clamping and anchored resize limits via half-plane algebra. - schemas: optional roofSegmentId on door/window; position is the segment-local wall mid-plane center, rotation[1] the face yaw. - cut: reuses capabilities.roofAccessory.buildCut; new cutScope: 'wall' subtracts from the wall brush only. cascadesViaHostSegment keeps the roof-merge loop from consuming door/window dirty marks (their own systems cascade via parentId). - tools: roof:* handlers in door/window tool + move-tool (the Build-tab preset path), with roofSegmentId cleared/restored across every roof<->wall re-anchor and revert; shared hit resolver normalizes normals through world space (merged mesh vs painted segment frames). - fix: RoofSystem no longer rebuilds per-segment CSG in accessory-reveal mode — the uncut rebuild used to draw over the merged shell's fresh opening until deselect. - fix: the walkthrough collider world now prunes by renderer-effective visibility; stale uncut segment CSG inside the hidden segments-wrapper blocked the player at openings the merged shell had cut through. Known gap: painted segments render per-segment CSG without accessory cuts (pre-existing, also affects skylight/dormer). Twice Codex-reviewed; details in private-editor plans/editor-roof-wall-openings.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
50304d7d10 |
Support template scans and room preset workflows (#390)
* Allow template forks to preserve scans * Add room preset save trigger * Show room preset action for selected zones * Use standard inspector for selected zones * Support room preset editor workflows * Preview room clear-underneath items |
||
|
|
265acdb2be | fix: improve ceiling handle feedback and WebGPU placeholders | ||
|
|
812b7306e8 |
Fix editor interaction, loading, and IFC cleanup (#385)
* fix: update site labels after camera swaps * fix: gate scene display until viewer is ready * fix: render scene loader on first paint * fix: keep loader during pending scene graph * feat: add wasd camera panning * feat: add x delete mode shortcut * feat: add floorplan north compass * fix: move floorplan compass to lower corner * fix: progressively rebuild heavy scene geometry * fix: simplify noisy ifc wall output |