Skip to content
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3731eb3
Add roof surface placement support for items
sudhir9297 May 18, 2026
ed53bc2
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
fd8e02c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
7c1e383
fixed conflict
sudhir9297 May 20, 2026
b3377da
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
f177a65
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 22, 2026
9af7491
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 22, 2026
fd27524
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 27, 2026
b516298
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 28, 2026
ebfc8ce
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 3, 2026
b7b313b
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 4, 2026
b2ad645
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 4, 2026
bffdb4a
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 8, 2026
ee7b10c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 9, 2026
7d4b474
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 10, 2026
3a3318c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 13, 2026
26df69f
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 17, 2026
5376e07
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 22, 2026
d2204aa
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 23, 2026
f2a5186
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 29, 2026
5841052
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 1, 2026
a6acaa3
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 8, 2026
cb9d7e6
feat(editor): add measurement tool
sudhir9297 Jul 8, 2026
96a4d32
feat(editor): add quick measurement modifier
sudhir9297 Jul 9, 2026
5bc1ded
feat(editor): add measurement axis lock
sudhir9297 Jul 9, 2026
f5951d3
feat(editor): add measurement snap feedback
sudhir9297 Jul 9, 2026
e0f8ece
feat(editor): add measurement helper hints
sudhir9297 Jul 9, 2026
9a51533
Enhance measurement tool snapping
sudhir9297 Jul 9, 2026
c39dd19
feat(editor): enhance 2D and 3D measurement tools with improved label…
sudhir9297 Jul 10, 2026
580aeff
feat(editor): update measurement tool to include precision cycling an…
sudhir9297 Jul 10, 2026
e0fec5b
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 10, 2026
fa6fd1d
Enhance measurement tool visuals
sudhir9297 Jul 10, 2026
bed452b
Enhance measurement tool overlays
sudhir9297 Jul 10, 2026
be2b632
Improve measurement tool references
sudhir9297 Jul 11, 2026
e368041
Align measurement angle baselines with registry
sudhir9297 Jul 11, 2026
a5ace1d
Format measurement tool store
sudhir9297 Jul 11, 2026
7fa9276
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 13, 2026
cda81a1
Merge branch 'main' into feat/measurement-tool
sudhir9297 Jul 13, 2026
5d87835
Refine measurement picking and stroke widths
sudhir9297 Jul 13, 2026
913d302
feat(editor): link measurement endpoints while dragging
sudhir9297 Jul 14, 2026
d834118
Add attached measurement scene graph support
sudhir9297 Jul 14, 2026
ab131b0
Refactor measurement scene graph sync
sudhir9297 Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/editor/components/build-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type BuildToolKind =
| 'ceiling'
| 'roof'
| 'stair'
| 'measurement'
| 'elevator'
| 'door'
| 'window'
Expand Down Expand Up @@ -74,6 +75,7 @@ const BUILD_TYPES: BuildType[] = [
{ id: 'ceiling', label: 'Ceiling', iconSrc: '/icons/ceiling.webp', kind: 'ceiling' },
{ id: 'roof', label: 'Roof', iconSrc: '/icons/roof.webp', kind: 'roof' },
{ id: 'stair', label: 'Stairs', iconSrc: '/icons/stairs.webp', kind: 'stair' },
{ id: 'measurement', label: 'Measure', iconSrc: '/icons/blueprint.webp', kind: 'measurement' },
{ id: 'elevator', label: 'Elevator', iconSrc: '/icons/elevator.webp', kind: 'elevator' },
{ id: 'door', label: 'Door', iconSrc: '/icons/door.webp', kind: 'door' },
{ id: 'window', label: 'Window', iconSrc: '/icons/window.webp', kind: 'window' },
Expand Down
1 change: 1 addition & 0 deletions apps/editor/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
allowedDevOrigins: ['127.0.0.1'],
logging: {
browserToTerminal: true,
},
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/hooks/scene-registry/scene-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ export const sceneRegistry = {
// Master lookup: ID -> Object3D
nodes: new Map<string, THREE.Object3D>(),

// Reverse ownership lookup used to keep nested R3F events attached to the
// closest registered scene node instead of bubbling into container nodes.
nodeIds: new WeakMap<THREE.Object3D, string>(),

// Categorized lookups: Kind -> Set of IDs. Backed by a Proxy so any kind
// gets a Set on first touch — no hardcoded list.
byType: byTypeProxy,

/** Remove all entries. Call when unloading a scene to prevent stale 3D refs. */
clear() {
this.nodes.clear()
this.nodeIds = new WeakMap<THREE.Object3D, string>()
for (const set of byTypeStore.values()) {
set.clear()
}
Expand All @@ -64,13 +69,15 @@ export function useRegistry(id: string, type: string, ref: React.RefObject<THREE

// 1. Add to master map
sceneRegistry.nodes.set(id, obj)
sceneRegistry.nodeIds.set(obj, id)

// 2. Add to type-specific set — Proxy auto-creates on first access.
sceneRegistry.byType[type]!.add(id)

// 3. Cleanup when component unmounts
return () => {
sceneRegistry.nodes.delete(id)
sceneRegistry.nodeIds.delete(obj)
sceneRegistry.byType[type]!.delete(id)
}
}, [id, type, ref])
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ export type {
LazyComponent,
LiveTransformLike,
McpOverrides,
MeasurementDefinition,
MeasurementDefinitionArea,
MeasurementDefinitionDirectLength,
MeasurementDefinitionPerimeter,
MeasurementDefinitionPoint,
MeasurementDefinitionSnapAnchor,
MeasurementDefinitionSnapGeometry,
MeasurementDefinitionSnapKind,
MeasurementDefinitionSnapSegment,
Modifiers,
MovableConfig,
MovableParentFrame,
Expand Down
76 changes: 76 additions & 0 deletions packages/core/src/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,76 @@ export type SnapProfile = 'item' | 'structural'
*/
export type BakePolicy = 'static' | 'strip' | 'replace'

export type MeasurementDefinitionPoint = readonly [number, number, number]

export type MeasurementDefinitionSnapKind =
| 'center'
| 'edge'
| 'endpoint'
| 'grid'
| 'guide'
| 'intersection'
| 'measurement'
| 'midpoint'
| 'surface'
| 'vertex'

export type MeasurementDefinitionSnapAnchor = {
kind?: MeasurementDefinitionSnapKind
label: string
point: MeasurementDefinitionPoint
priority?: number
targetLine?: {
end: MeasurementDefinitionPoint
start: MeasurementDefinitionPoint
}
}

export type MeasurementDefinitionSnapSegment = {
kind?: MeasurementDefinitionSnapKind
label: string
sourceId?: string
start: MeasurementDefinitionPoint
end: MeasurementDefinitionPoint
priority?: number
}

export type MeasurementDefinitionSnapGeometry = {
anchors?: MeasurementDefinitionSnapAnchor[]
segments?: MeasurementDefinitionSnapSegment[]
}

export type MeasurementDefinitionDirectLength = {
end: MeasurementDefinitionPoint
measuredDistanceMeters: number
start: MeasurementDefinitionPoint
}

export type MeasurementDefinitionArea = {
areaSquareMeters: number
boundaryPoints?: ReadonlyArray<MeasurementDefinitionPoint>
labelPoint: MeasurementDefinitionPoint
}

export type MeasurementDefinitionPerimeter = {
boundaryPoints?: ReadonlyArray<MeasurementDefinitionPoint>
labelPoint: MeasurementDefinitionPoint
lengthMeters: number
}

export type MeasurementDefinition<N> = {
area?: (node: N, ctx: GeometryContext) => MeasurementDefinitionArea | null
directLength?: (
node: N,
ctx: GeometryContext,
cursorPoint?: MeasurementDefinitionPoint | null,
cursorNormal?: MeasurementDefinitionPoint | null,
) => MeasurementDefinitionDirectLength | null
perimeter?: (node: N, ctx: GeometryContext) => MeasurementDefinitionPerimeter | null
pick3D?: boolean
snapGeometry?: (node: N, ctx: GeometryContext) => MeasurementDefinitionSnapGeometry | null
}

export type ExportAnimationContext<N = AnyNode> = {
node: N
object: Object3D
Expand Down Expand Up @@ -806,6 +876,12 @@ export type NodeDefinition<S extends ZodObject<any>> = {
migrate?: Record<number, (old: unknown) => unknown>

capabilities: Capabilities
/**
* Per-kind geometry for editor measurement tools. This keeps measurement
* snapping, direct length, area, and perimeter behavior registry-driven:
* editor tools call this hook instead of branching on concrete node kinds.
*/
measurement?: MeasurementDefinition<z.infer<S>>
relations?: Relations
parametrics?: ParametricDescriptor<z.infer<S>>

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/systems/stair/stair-opening-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function getNodesWithLiveStairOpeningInputs(
liveTransforms: ReadonlyMap<string, LiveTransform>,
liveOverrides: ReadonlyMap<string, LiveNodeOverrides>,
previewSurfaceIds: ReadonlySet<string>,
) {
): Record<string, AnyNode> {
const nextNodes: Record<string, AnyNode> = { ...nodes }

for (const [nodeId, override] of liveOverrides) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ export const FloorplanGroupSelectionBox = memo(function FloorplanGroupSelectionB
// which lag the `nodes` commit by a frame or two.
const meshEpoch = useMeshSettleEpoch(nodes)
const box = useMemo(() => {
void meshEpoch
if (selectedIds.length < 2 || !levelId) return null
const participantIds = selectedIds.filter(
(id) => classifyParticipant(nodes[id as AnyNodeId], levelId, nodes) !== null,
Expand All @@ -627,7 +628,6 @@ export const FloorplanGroupSelectionBox = memo(function FloorplanGroupSelectionB
width: Math.abs(max.x - min.x),
depth: Math.abs(max.z - min.z),
}
// biome-ignore lint/correctness/useExhaustiveDependencies: meshEpoch re-measures settled meshes
}, [selectedIds, levelId, nodes, meshEpoch])

if (!box || movingNode || mode === 'delete') return null
Expand Down
Loading
Loading