@pascal-app/nodes 1.0.0-beta.3 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cabinet/flame-index.d.ts +3 -0
- package/dist/cabinet/flame-index.d.ts.map +1 -0
- package/dist/cabinet/flame-index.js +13 -0
- package/dist/cabinet/system.d.ts +2 -0
- package/dist/cabinet/system.d.ts.map +1 -1
- package/dist/cabinet/system.js +28 -8
- package/dist/cabinet/tool.d.ts.map +1 -1
- package/dist/cabinet/tool.js +2 -1
- package/dist/ceiling/panel.d.ts.map +1 -1
- package/dist/ceiling/panel.js +8 -2
- package/dist/ceiling/parametrics.js +1 -1
- package/dist/column/panel.js +1 -1
- package/dist/column/parametrics.js +1 -1
- package/dist/dormer/placement-guides.d.ts.map +1 -1
- package/dist/dormer/placement-guides.js +5 -4
- package/dist/elevator/definition.d.ts.map +1 -1
- package/dist/elevator/definition.js +2 -0
- package/dist/fence/curve-tool.d.ts.map +1 -1
- package/dist/fence/curve-tool.js +14 -9
- package/dist/fence/move-control-point-tool.d.ts.map +1 -1
- package/dist/fence/move-control-point-tool.js +13 -8
- package/dist/fence/move-tangent-tool.d.ts.map +1 -1
- package/dist/fence/move-tangent-tool.js +13 -8
- package/dist/fence/tool.d.ts.map +1 -1
- package/dist/fence/tool.js +8 -11
- package/dist/measurement/renderer.d.ts.map +1 -1
- package/dist/measurement/renderer.js +6 -5
- package/dist/measurement/surface-query.js +5 -5
- package/dist/measurement/tool.d.ts.map +1 -1
- package/dist/measurement/tool.js +7 -5
- package/dist/ridge-vent/renderer.d.ts.map +1 -1
- package/dist/ridge-vent/renderer.js +2 -2
- package/dist/shared/mep-ghost.d.ts.map +1 -1
- package/dist/shared/mep-ghost.js +19 -1
- package/dist/slab/panel.d.ts.map +1 -1
- package/dist/slab/panel.js +1 -1
- package/dist/wall/curve-tool.d.ts +3 -4
- package/dist/wall/curve-tool.d.ts.map +1 -1
- package/dist/wall/curve-tool.js +17 -13
- package/dist/wall/panel.d.ts.map +1 -1
- package/dist/wall/panel.js +56 -14
- package/dist/wall/parametrics.js +1 -1
- package/dist/wall/system.d.ts +3 -0
- package/dist/wall/system.d.ts.map +1 -1
- package/dist/wall/system.js +5 -1
- package/dist/wall/tool.d.ts.map +1 -1
- package/dist/wall/tool.js +24 -27
- package/dist/wall/wall-batch-system.d.ts +23 -0
- package/dist/wall/wall-batch-system.d.ts.map +1 -0
- package/dist/wall/wall-batch-system.js +389 -0
- package/dist/wall/wall-batch.d.ts +66 -0
- package/dist/wall/wall-batch.d.ts.map +1 -0
- package/dist/wall/wall-batch.js +169 -0
- package/dist/zone/quantities-panel.d.ts.map +1 -1
- package/dist/zone/quantities-panel.js +4 -3
- package/package.json +7 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flame-index.d.ts","sourceRoot":"","sources":["../../src/cabinet/flame-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAUrC,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAMrE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function isFlameObject(object) {
|
|
2
|
+
return Boolean(object.userData.cabinetFlameJet ||
|
|
3
|
+
object.userData.cabinetFlamePulse ||
|
|
4
|
+
object.userData.cabinetFlameMaterialPulse);
|
|
5
|
+
}
|
|
6
|
+
export function collectCabinetFlameObjects(root) {
|
|
7
|
+
const objects = [];
|
|
8
|
+
root.traverse((object) => {
|
|
9
|
+
if (isFlameObject(object))
|
|
10
|
+
objects.push(object);
|
|
11
|
+
});
|
|
12
|
+
return objects;
|
|
13
|
+
}
|
package/dist/cabinet/system.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type SceneApi } from '@pascal-app/core';
|
|
2
|
+
import type { Object3D } from 'three';
|
|
3
|
+
export declare function animateCabinetFlames(objects: Object3D[], elapsedTime: number, updateTubes: boolean): void;
|
|
2
4
|
/**
|
|
3
5
|
* Poses door hinges / drawer slides stamped with `userData.cabinetPose`
|
|
4
6
|
* directly, so `operationState` changes never trigger a geometry rebuild
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/cabinet/system.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,QAAQ,EAAiB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/cabinet/system.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,QAAQ,EAAiB,MAAM,kBAAkB,CAAA;AAGjG,OAAO,KAAK,EAAmC,QAAQ,EAAE,MAAM,OAAO,CAAA;AAgBtE,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,QAAQ,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,OAAO,QAgCrB;AAED;;;;;;GAMG;AACH,QAAA,MAAM,sBAAsB,GAAI,cAAc;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,SAuFnE,CAAA;AAED,eAAe,sBAAsB,CAAA"}
|
package/dist/cabinet/system.js
CHANGED
|
@@ -5,22 +5,23 @@ import { useRef } from 'react';
|
|
|
5
5
|
import { poseCabinetMovingParts } from './animation';
|
|
6
6
|
import { updateCooktopFlameTube } from './cooktop-flame';
|
|
7
7
|
import { bumpCabinetRunsNear, cabinetRunFootprint, cabinetRunNeighborSignature, } from './definition';
|
|
8
|
+
import { collectCabinetFlameObjects } from './flame-index';
|
|
8
9
|
function materialWithOpacity(material) {
|
|
9
10
|
if (!material)
|
|
10
11
|
return null;
|
|
11
12
|
return Array.isArray(material) ? (material[0] ?? null) : material;
|
|
12
13
|
}
|
|
13
|
-
function animateCabinetFlames(
|
|
14
|
-
|
|
14
|
+
export function animateCabinetFlames(objects, elapsedTime, updateTubes) {
|
|
15
|
+
for (const obj of objects) {
|
|
15
16
|
const jet = obj.userData.cabinetFlameJet;
|
|
16
17
|
if (jet) {
|
|
17
18
|
if (!updateTubes)
|
|
18
|
-
|
|
19
|
+
continue;
|
|
19
20
|
const mesh = obj;
|
|
20
21
|
const position = mesh.geometry.getAttribute('position');
|
|
21
22
|
updateCooktopFlameTube(position.array, elapsedTime, jet.seed, jet.burnerR);
|
|
22
23
|
position.needsUpdate = true;
|
|
23
|
-
|
|
24
|
+
continue;
|
|
24
25
|
}
|
|
25
26
|
const pulse = obj.userData.cabinetFlamePulse;
|
|
26
27
|
if (pulse) {
|
|
@@ -28,14 +29,14 @@ function animateCabinetFlames(root, elapsedTime, updateTubes) {
|
|
|
28
29
|
}
|
|
29
30
|
const materialPulse = obj.userData.cabinetFlameMaterialPulse;
|
|
30
31
|
if (!materialPulse)
|
|
31
|
-
|
|
32
|
+
continue;
|
|
32
33
|
const material = materialWithOpacity(obj.material);
|
|
33
34
|
if (!material || !('opacity' in material))
|
|
34
|
-
|
|
35
|
+
continue;
|
|
35
36
|
material.opacity =
|
|
36
37
|
materialPulse.base +
|
|
37
38
|
materialPulse.amplitude * Math.sin(elapsedTime * 2.3 + materialPulse.phase);
|
|
38
|
-
}
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
42
|
* Poses door hinges / drawer slides stamped with `userData.cabinetPose`
|
|
@@ -47,6 +48,7 @@ function animateCabinetFlames(root, elapsedTime, updateTubes) {
|
|
|
47
48
|
const CabinetAnimationSystem = ({ sceneApi }) => {
|
|
48
49
|
const appliedRef = useRef(new Map());
|
|
49
50
|
const lastTubeUpdateRef = useRef(0);
|
|
51
|
+
const flameObjectsRef = useRef(new Map());
|
|
50
52
|
// Last-seen neighbor-affecting signature per run. A run whose countertop
|
|
51
53
|
// overhang trims against sibling runs never sees a neighbor's move in its
|
|
52
54
|
// own geometryKey, so when a run's signature changes here we bump the
|
|
@@ -107,9 +109,27 @@ const CabinetAnimationSystem = ({ sceneApi }) => {
|
|
|
107
109
|
poseCabinetMovingParts(obj, value);
|
|
108
110
|
applied.set(id, value);
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
let flameEntry = flameObjectsRef.current.get(id);
|
|
113
|
+
const childrenChanged = !flameEntry ||
|
|
114
|
+
flameEntry.children.length !== obj.children.length ||
|
|
115
|
+
flameEntry.children.some((child, index) => child !== obj.children[index]);
|
|
116
|
+
if (flameEntry?.root !== obj || childrenChanged) {
|
|
117
|
+
flameEntry = {
|
|
118
|
+
root: obj,
|
|
119
|
+
children: [...obj.children],
|
|
120
|
+
objects: collectCabinetFlameObjects(obj),
|
|
121
|
+
};
|
|
122
|
+
flameObjectsRef.current.set(id, flameEntry);
|
|
123
|
+
}
|
|
124
|
+
if (flameEntry.objects.length > 0) {
|
|
125
|
+
animateCabinetFlames(flameEntry.objects, clock.elapsedTime, updateTubes);
|
|
126
|
+
}
|
|
111
127
|
}
|
|
112
128
|
}
|
|
129
|
+
for (const id of flameObjectsRef.current.keys()) {
|
|
130
|
+
if (!sceneRegistry.nodes.has(id))
|
|
131
|
+
flameObjectsRef.current.delete(id);
|
|
132
|
+
}
|
|
113
133
|
}, 2);
|
|
114
134
|
return null;
|
|
115
135
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/cabinet/tool.tsx"],"names":[],"mappings":"AA+PA,QAAA,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/cabinet/tool.tsx"],"names":[],"mappings":"AA+PA,QAAA,MAAM,WAAW,0CA49BhB,CAAA;AAED,eAAe,WAAW,CAAA"}
|
package/dist/cabinet/tool.js
CHANGED
|
@@ -122,6 +122,7 @@ function wallHitFromWallEvent(event) {
|
|
|
122
122
|
const CabinetTool = () => {
|
|
123
123
|
const activeLevelId = useViewer((s) => s.selection.levelId);
|
|
124
124
|
const unit = useViewer((s) => s.unit);
|
|
125
|
+
const metricNotation = useViewer((s) => s.metricNotation);
|
|
125
126
|
const [placement, setPlacement] = useState(null);
|
|
126
127
|
const [draftSegments, setDraftSegments] = useState([]);
|
|
127
128
|
const [yaw, setYaw] = useState(0);
|
|
@@ -917,7 +918,7 @@ const CabinetTool = () => {
|
|
|
917
918
|
visualPosition[1],
|
|
918
919
|
placementBoxPlanPosition[2],
|
|
919
920
|
];
|
|
920
|
-
return (_jsxs(LevelOffsetGroup, { children: [placement.guide && _jsx(WallSnapGuide, { blocked: !placement.valid, guide: placement.guide }), _jsx(PlacementBox, { dimensions: placementBoxDimensions, measurements: { unit }, position: placementBoxPosition, rotationY: placementRotationY, valid: placement.valid }), draftSegments.map((segment, segmentIndex) => (_jsx("group", { position: segment.anchor.position, rotation: [0, segment.anchor.yaw, 0], children: segment.stretch.modules.map((module, index) => (_jsx("group", { position: [module.x, 0, 0], scale: [module.width / previewNode.width, 1, 1], children: _jsx("primitive", { object: ghostForIndex(draftModuleOffsets[segmentIndex] + index) }) }, `${segmentIndex}-${index}`))) }, `draft-${segmentIndex}`))), _jsx("group", { ref: activeGhostRef, position: visualPosition, rotation: [0, placementRotationY, 0], children: stretch ? (stretch.modules.map((module, index) => (_jsx("group", { position: [module.x, 0, 0], scale: [module.width / previewNode.width, 1, 1], children: _jsx("primitive", { object: ghostForIndex(activeStretchGhostOffset + index) }) }, index)))) : (_jsx("primitive", { object: ghost })) }), placementLabel ? (_jsx(Html, { center: true, position: [
|
|
921
|
+
return (_jsxs(LevelOffsetGroup, { children: [placement.guide && _jsx(WallSnapGuide, { blocked: !placement.valid, guide: placement.guide }), _jsx(PlacementBox, { dimensions: placementBoxDimensions, measurements: { unit, metricNotation }, position: placementBoxPosition, rotationY: placementRotationY, valid: placement.valid }), draftSegments.map((segment, segmentIndex) => (_jsx("group", { position: segment.anchor.position, rotation: [0, segment.anchor.yaw, 0], children: segment.stretch.modules.map((module, index) => (_jsx("group", { position: [module.x, 0, 0], scale: [module.width / previewNode.width, 1, 1], children: _jsx("primitive", { object: ghostForIndex(draftModuleOffsets[segmentIndex] + index) }) }, `${segmentIndex}-${index}`))) }, `draft-${segmentIndex}`))), _jsx("group", { ref: activeGhostRef, position: visualPosition, rotation: [0, placementRotationY, 0], children: stretch ? (stretch.modules.map((module, index) => (_jsx("group", { position: [module.x, 0, 0], scale: [module.width / previewNode.width, 1, 1], children: _jsx("primitive", { object: ghostForIndex(activeStretchGhostOffset + index) }) }, index)))) : (_jsx("primitive", { object: ghost })) }), placementLabel ? (_jsx(Html, { center: true, position: [
|
|
921
922
|
labelPosition[0],
|
|
922
923
|
previewNode.plinthHeight + previewNode.carcassHeight + 0.35,
|
|
923
924
|
labelPosition[2],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/ceiling/panel.tsx"],"names":[],"mappings":"AA2BA;;;;;;;GAOG;AACH,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/ceiling/panel.tsx"],"names":[],"mappings":"AA2BA;;;;;;;GAOG;AACH,wBAAgB,YAAY,uCA6W3B;AAED,eAAe,YAAY,CAAA"}
|
package/dist/ceiling/panel.js
CHANGED
|
@@ -16,6 +16,7 @@ import { useCallback, useEffect, useRef } from 'react';
|
|
|
16
16
|
export function CeilingPanel() {
|
|
17
17
|
const selectedId = useViewer((s) => s.selection.selectedIds[0]);
|
|
18
18
|
const unit = useViewer((s) => s.unit);
|
|
19
|
+
const metricNotation = useViewer((s) => s.metricNotation);
|
|
19
20
|
const setSelection = useViewer((s) => s.setSelection);
|
|
20
21
|
const editingHole = useEditingHole();
|
|
21
22
|
const setMovingNode = useEditor((s) => s.setMovingNode);
|
|
@@ -30,7 +31,7 @@ export function CeilingPanel() {
|
|
|
30
31
|
const parent = node?.parentId ? s.nodes[node.parentId] : undefined;
|
|
31
32
|
return parent?.type === 'level'
|
|
32
33
|
? getCeilingClampBound(parent.id, s.nodes, node?.polygon ?? [])
|
|
33
|
-
:
|
|
34
|
+
: Number.POSITIVE_INFINITY;
|
|
34
35
|
});
|
|
35
36
|
// Effective height: the stored custom height, or — for follows-mode
|
|
36
37
|
// ceilings (absent `height`) — the live level-top bound. Primitive
|
|
@@ -184,7 +185,12 @@ export function CeilingPanel() {
|
|
|
184
185
|
return (_jsxs(PanelWrapper, { icon: "/icons/ceiling.webp", onClose: handleClose, title: node.name || 'Ceiling', width: 320, children: [_jsxs(PanelSection, { title: "Height", children: [_jsx(SegmentedControl, { onChange: handleTopModeChange, options: [
|
|
185
186
|
{ label: 'Follows level', value: 'storey' },
|
|
186
187
|
{ label: 'Custom height', value: 'custom' },
|
|
187
|
-
], value: isFollows ? 'storey' : 'custom' }), isFollows ? (_jsxs("div", { className: "px-1 text-[11px] text-muted-foreground", children: ["Currently ", formatLinearMeasurement(resolvedHeight, unit)] })) : (_jsx(SliderControl, { label: "Height", max: Math.min(
|
|
188
|
+
], value: isFollows ? 'storey' : 'custom' }), isFollows ? (_jsxs("div", { className: "px-1 text-[11px] text-muted-foreground", children: ["Currently ", formatLinearMeasurement(resolvedHeight, unit, metricNotation)] })) : (_jsx(SliderControl, { label: "Height", max: Math.min(20, maxHeight), min: 0, onChange: handleHeightChange, precision: 3, step: 0.01, unit: "m", value: Math.round((node.height ?? resolvedHeight) * 1000) / 1000 })), _jsx("div", { className: "mt-2 grid grid-cols-3 gap-1.5 px-1 pb-1", children: heightPresets.map((preset) => {
|
|
189
|
+
const fits = preset.height <= maxHeight;
|
|
190
|
+
return (_jsx(ActionButton, { className: fits ? undefined : 'cursor-not-allowed opacity-40', disabled: !fits, label: preset.label, onClick: () => handleHeightChange(preset.height), title: fits
|
|
191
|
+
? undefined
|
|
192
|
+
: `Taller than this level (${formatLinearMeasurement(maxHeight, unit, metricNotation)} available). Raise the level height first.` }, preset.label));
|
|
193
|
+
}) }), Number.isFinite(maxHeight) && (_jsxs("div", { className: "px-1 pb-1 text-[11px] text-muted-foreground", children: ["Limited by the level to ", formatLinearMeasurement(maxHeight, unit, metricNotation), " \u2014 raise the level height for a taller ceiling."] }))] }), _jsx(PanelSection, { title: "Info", children: _jsxs("div", { className: "flex items-center justify-between px-2 py-1 text-muted-foreground text-sm", children: [_jsx("span", { children: "Area" }), _jsxs("span", { className: "font-mono text-white", children: [area.toFixed(2), " m\u00B2"] })] }) }), _jsxs(PanelSection, { title: "Holes", children: [node.holes && node.holes.length > 0 ? (_jsx("div", { className: "flex flex-col gap-1 pb-2", children: node.holes.map((hole, index) => {
|
|
188
194
|
const holeArea = calculateArea(hole);
|
|
189
195
|
const isEditing = editingHole?.nodeId === selectedId && editingHole?.holeIndex === index;
|
|
190
196
|
const source = node.holeMetadata?.[index]?.source ?? 'manual';
|
|
@@ -9,7 +9,7 @@ export const ceilingParametrics = {
|
|
|
9
9
|
groups: [
|
|
10
10
|
{
|
|
11
11
|
label: 'Dimensions',
|
|
12
|
-
fields: [{ key: 'height', kind: 'number', unit: 'm', min: 1.5, max:
|
|
12
|
+
fields: [{ key: 'height', kind: 'number', unit: 'm', min: 1.5, max: 20, step: 0.05 }],
|
|
13
13
|
},
|
|
14
14
|
],
|
|
15
15
|
customPanel: () => import('./panel'),
|
package/dist/column/panel.js
CHANGED
|
@@ -313,7 +313,7 @@ export default function ColumnPanel() {
|
|
|
313
313
|
if (!event.target.value)
|
|
314
314
|
return;
|
|
315
315
|
handleUpdate(proportionUpdates(node, event.target.value));
|
|
316
|
-
}, value: "", children: [_jsx("option", { value: "", children: "Apply proportion..." }), COLUMN_PROPORTION_OPTIONS.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] })), _jsx(SliderControl, { label: "Height", max:
|
|
316
|
+
}, value: "", children: [_jsx("option", { value: "", children: "Apply proportion..." }), COLUMN_PROPORTION_OPTIONS.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] })), _jsx(SliderControl, { label: "Height", max: 20, min: 0.8, onChange: (value) => handleUpdate({ height: value }), precision: 2, step: 0.05, unit: "m", value: node.height }), isBraceSupport ? (_jsxs(_Fragment, { children: [(supportStyle === 'a-frame' ||
|
|
317
317
|
supportStyle === 'x-brace' ||
|
|
318
318
|
supportStyle === 'k-brace' ||
|
|
319
319
|
supportStyle === 'single-strut' ||
|
|
@@ -11,7 +11,7 @@ export const columnParametrics = {
|
|
|
11
11
|
{
|
|
12
12
|
label: 'Dimensions',
|
|
13
13
|
fields: [
|
|
14
|
-
{ key: 'height', kind: 'number', unit: 'm', min: 0.5, max:
|
|
14
|
+
{ key: 'height', kind: 'number', unit: 'm', min: 0.5, max: 20, step: 0.05 },
|
|
15
15
|
{ key: 'width', kind: 'number', unit: 'm', min: 0.1, max: 2, step: 0.01 },
|
|
16
16
|
{ key: 'depth', kind: 'number', unit: 'm', min: 0.1, max: 2, step: 0.01 },
|
|
17
17
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placement-guides.d.ts","sourceRoot":"","sources":["../../src/dormer/placement-guides.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AA0CvD,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAgBpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,MAAM,EACN,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,eAAe,CAAA;IACxB,MAAM,EAAE,IAAI,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,+
|
|
1
|
+
{"version":3,"file":"placement-guides.d.ts","sourceRoot":"","sources":["../../src/dormer/placement-guides.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AA0CvD,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAgBpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,MAAM,EACN,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,eAAe,CAAA;IACxB,MAAM,EAAE,IAAI,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,+BAkGA"}
|
|
@@ -52,6 +52,7 @@ const alignMaterial = new LineBasicNodeMaterial({
|
|
|
52
52
|
*/
|
|
53
53
|
export function DormerPlacementGuides({ segment, center, width, depth, rotation, movingId, }) {
|
|
54
54
|
const unit = useViewer((s) => s.unit);
|
|
55
|
+
const metricNotation = useViewer((s) => s.metricNotation);
|
|
55
56
|
const [cx, , cz] = center;
|
|
56
57
|
const faceBounds = getRoofSurfaceFaceBoundsAt(segment, cx, cz);
|
|
57
58
|
const halfW = Math.max(0, width) / 2;
|
|
@@ -139,13 +140,13 @@ export function DormerPlacementGuides({ segment, center, width, depth, rotation,
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
guides.push(...siblingSpacing.guides);
|
|
142
|
-
return (_jsx(_Fragment, { children: guides.map((g) => (_jsx(Guide, { guide: g, unit: unit }, g.id))) }));
|
|
143
|
+
return (_jsx(_Fragment, { children: guides.map((g) => (_jsx(Guide, { guide: g, metricNotation: metricNotation, unit: unit }, g.id))) }));
|
|
143
144
|
}
|
|
144
|
-
function Guide({ guide, unit }) {
|
|
145
|
+
function Guide({ guide, metricNotation, unit, }) {
|
|
145
146
|
if (guide.kind === 'badge') {
|
|
146
|
-
return _jsx(GuideBadge, { at: guide.at, pill: `= ${formatMeasurement(guide.value, unit)}` });
|
|
147
|
+
return (_jsx(GuideBadge, { at: guide.at, pill: `= ${formatMeasurement(guide.value, unit, metricNotation)}` }));
|
|
147
148
|
}
|
|
148
|
-
return (_jsx(GuideLine, { from: guide.from, kind: guide.kind, pill: guide.value === undefined ? undefined : formatMeasurement(guide.value, unit), to: guide.to }));
|
|
149
|
+
return (_jsx(GuideLine, { from: guide.from, kind: guide.kind, pill: guide.value === undefined ? undefined : formatMeasurement(guide.value, unit, metricNotation), to: guide.to }));
|
|
149
150
|
}
|
|
150
151
|
function GuideBadge({ at, pill }) {
|
|
151
152
|
return (_jsx(Html, { center: true, position: at, style: { pointerEvents: 'none', userSelect: 'none' }, zIndexRange: [20, 0], children: _jsx("div", { className: "whitespace-nowrap rounded-[3px] px-[5px] py-[2px] font-semibold font-sans text-[11px] text-white", style: { backgroundColor: BADGE_BG }, children: pill }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/elevator/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AAKzB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAsJvC;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,OAAO,YAAY,
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/elevator/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AAKzB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAsJvC;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,OAAO,YAAY,CA8GlE,CAAA"}
|
|
@@ -218,6 +218,8 @@ export const elevatorDefinition = {
|
|
|
218
218
|
},
|
|
219
219
|
parametrics: elevatorParametrics,
|
|
220
220
|
handles: elevatorHandles,
|
|
221
|
+
// No dirty consumer rebuilds this kind — see NodeDefinition.dirtyTracking.
|
|
222
|
+
dirtyTracking: false,
|
|
221
223
|
renderer: {
|
|
222
224
|
kind: 'parametric',
|
|
223
225
|
module: () => import('./renderer'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curve-tool.d.ts","sourceRoot":"","sources":["../../src/fence/curve-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"curve-tool.d.ts","sourceRoot":"","sources":["../../src/fence/curve-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,SAAS,EAQf,MAAM,kBAAkB,CAAA;AAYzB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAyIxD,CAAA;AAED,eAAe,cAAc,CAAA"}
|
package/dist/fence/curve-tool.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { emitter, getClampedWallCurveOffset, getMaxWallCurveOffset, getWallChordFrame, getWallMidpointHandlePoint, normalizeWallCurveOffset,
|
|
3
|
+
import { acquireSceneHistoryPause, emitter, getClampedWallCurveOffset, getMaxWallCurveOffset, getWallChordFrame, getWallMidpointHandlePoint, normalizeWallCurveOffset, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { CursorSphere, getSegmentGridStep, markToolCancelConsumed, snapScalarToGrid, triggerSFX, useInteractionScope, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
@@ -36,8 +36,8 @@ export const CurveFenceTool = ({ node }) => {
|
|
|
36
36
|
const originalCurveOffset = originalCurveOffsetRef.current;
|
|
37
37
|
const chord = getWallChordFrame(node);
|
|
38
38
|
const maxCurveOffset = getMaxWallCurveOffset(node);
|
|
39
|
-
|
|
40
|
-
let
|
|
39
|
+
let releaseHistory = acquireSceneHistoryPause(useScene);
|
|
40
|
+
let wasFinalized = false;
|
|
41
41
|
const applyPreview = (curveOffset) => {
|
|
42
42
|
if (previewOffsetRef.current === curveOffset) {
|
|
43
43
|
return;
|
|
@@ -76,21 +76,23 @@ export const CurveFenceTool = ({ node }) => {
|
|
|
76
76
|
applyPreview(nextCurveOffset);
|
|
77
77
|
};
|
|
78
78
|
const onGridClick = (event) => {
|
|
79
|
+
if (wasFinalized)
|
|
80
|
+
return;
|
|
79
81
|
if (Date.now() - activatedAtRef.current < 150) {
|
|
80
82
|
event.nativeEvent?.stopPropagation?.();
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
const curveOffset = previewOffsetRef.current;
|
|
84
|
-
|
|
86
|
+
wasFinalized = true;
|
|
85
87
|
if (curveOffset !== originalCurveOffset) {
|
|
86
88
|
// Restore original baseline while paused so the next resume+update
|
|
87
89
|
// registers as a single tracked change (undo reverts to original).
|
|
88
90
|
useScene.getState().updateNode(nodeId, { curveOffset: originalCurveOffset });
|
|
89
91
|
useScene.getState().markDirty(nodeId);
|
|
90
|
-
|
|
92
|
+
releaseHistory();
|
|
91
93
|
useScene.getState().updateNode(nodeId, { curveOffset });
|
|
92
94
|
useScene.getState().markDirty(nodeId);
|
|
93
|
-
|
|
95
|
+
releaseHistory = acquireSceneHistoryPause(useScene);
|
|
94
96
|
}
|
|
95
97
|
triggerSFX('sfx:item-place');
|
|
96
98
|
useViewer.getState().setSelection({ selectedIds: [nodeId] });
|
|
@@ -98,9 +100,12 @@ export const CurveFenceTool = ({ node }) => {
|
|
|
98
100
|
event.nativeEvent?.stopPropagation?.();
|
|
99
101
|
};
|
|
100
102
|
const onCancel = () => {
|
|
103
|
+
if (wasFinalized)
|
|
104
|
+
return;
|
|
101
105
|
restoreOriginal();
|
|
106
|
+
wasFinalized = true;
|
|
102
107
|
useViewer.getState().setSelection({ selectedIds: [nodeId] });
|
|
103
|
-
|
|
108
|
+
releaseHistory();
|
|
104
109
|
markToolCancelConsumed();
|
|
105
110
|
exitCurveMode();
|
|
106
111
|
};
|
|
@@ -108,10 +113,10 @@ export const CurveFenceTool = ({ node }) => {
|
|
|
108
113
|
emitter.on('grid:click', onGridClick);
|
|
109
114
|
emitter.on('tool:cancel', onCancel);
|
|
110
115
|
return () => {
|
|
111
|
-
if (!
|
|
116
|
+
if (!wasFinalized) {
|
|
112
117
|
restoreOriginal();
|
|
113
118
|
}
|
|
114
|
-
|
|
119
|
+
releaseHistory();
|
|
115
120
|
emitter.off('grid:move', onGridMove);
|
|
116
121
|
emitter.off('grid:click', onGridClick);
|
|
117
122
|
emitter.off('tool:cancel', onCancel);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-control-point-tool.d.ts","sourceRoot":"","sources":["../../src/fence/move-control-point-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"move-control-point-tool.d.ts","sourceRoot":"","sources":["../../src/fence/move-control-point-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,SAAS,EAIf,MAAM,kBAAkB,CAAA;AAazB,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/C,MAAM,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C,CAuGA,CAAA;AAED,eAAe,yBAAyB,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { acquireSceneHistoryPause, emitter, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { CursorSphere, getSegmentGridStep, isGridSnapActive, markToolCancelConsumed, snapScalarToGrid, triggerSFX, useInteractionScope, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
@@ -15,8 +15,8 @@ export const MoveFenceControlPointTool = ({ target }) => {
|
|
|
15
15
|
originalPoint[1],
|
|
16
16
|
]);
|
|
17
17
|
useEffect(() => {
|
|
18
|
-
|
|
19
|
-
let
|
|
18
|
+
const releaseHistory = acquireSceneHistoryPause(useScene);
|
|
19
|
+
let finalized = false;
|
|
20
20
|
let lastPoint = [originalPoint[0], originalPoint[1]];
|
|
21
21
|
const buildPatch = (point) => {
|
|
22
22
|
const nextPath = originalPath.map((pathPoint, pathIndex) => pathIndex === index ? point : pathPoint);
|
|
@@ -59,8 +59,10 @@ export const MoveFenceControlPointTool = ({ target }) => {
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
const onGridClick = (event) => {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
if (finalized)
|
|
63
|
+
return;
|
|
64
|
+
finalized = true;
|
|
65
|
+
releaseHistory();
|
|
64
66
|
useScene.getState().updateNode(fenceId, buildPatch(lastPoint));
|
|
65
67
|
useLiveNodeOverrides.getState().clear(fenceId);
|
|
66
68
|
useScene.getState().markDirty(fenceId);
|
|
@@ -68,8 +70,11 @@ export const MoveFenceControlPointTool = ({ target }) => {
|
|
|
68
70
|
event.nativeEvent?.stopPropagation?.();
|
|
69
71
|
};
|
|
70
72
|
const onCancel = () => {
|
|
73
|
+
if (finalized)
|
|
74
|
+
return;
|
|
71
75
|
restore();
|
|
72
|
-
|
|
76
|
+
finalized = true;
|
|
77
|
+
releaseHistory();
|
|
73
78
|
markToolCancelConsumed();
|
|
74
79
|
exit(false);
|
|
75
80
|
};
|
|
@@ -77,9 +82,9 @@ export const MoveFenceControlPointTool = ({ target }) => {
|
|
|
77
82
|
emitter.on('grid:click', onGridClick);
|
|
78
83
|
emitter.on('tool:cancel', onCancel);
|
|
79
84
|
return () => {
|
|
80
|
-
if (!
|
|
85
|
+
if (!finalized) {
|
|
81
86
|
restore();
|
|
82
|
-
|
|
87
|
+
releaseHistory();
|
|
83
88
|
}
|
|
84
89
|
emitter.off('grid:move', onGridMove);
|
|
85
90
|
emitter.off('grid:click', onGridClick);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-tangent-tool.d.ts","sourceRoot":"","sources":["../../src/fence/move-tangent-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"move-tangent-tool.d.ts","sourceRoot":"","sources":["../../src/fence/move-tangent-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,SAAS,EAIf,MAAM,kBAAkB,CAAA;AAezB,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC;IAC1C,MAAM,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAE,CAAA;CAChE,CAwGA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { acquireSceneHistoryPause, emitter, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { CursorSphere, getSegmentGridStep, isGridSnapActive, markToolCancelConsumed, snapScalarToGrid, triggerSFX, useInteractionScope, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
@@ -11,8 +11,8 @@ export const MoveFenceTangentTool = ({ target }) => {
|
|
|
11
11
|
const anchor = target.fence.path?.[index] ?? target.fence.start;
|
|
12
12
|
const [cursor, setCursor] = useState([anchor[0], 0, anchor[1]]);
|
|
13
13
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
let
|
|
14
|
+
const releaseHistory = acquireSceneHistoryPause(useScene);
|
|
15
|
+
let finalized = false;
|
|
16
16
|
const originalTangents = (target.fence.tangents ?? []).map((t) => (t ? [t[0], t[1]] : null));
|
|
17
17
|
let lastTangents = originalTangents;
|
|
18
18
|
const writeTangent = (vector) => {
|
|
@@ -54,9 +54,11 @@ export const MoveFenceTangentTool = ({ target }) => {
|
|
|
54
54
|
writeTangent([armX / TANGENT_HANDLE_ARM_SCALE, armZ / TANGENT_HANDLE_ARM_SCALE]);
|
|
55
55
|
};
|
|
56
56
|
const onGridClick = (event) => {
|
|
57
|
-
|
|
57
|
+
if (finalized)
|
|
58
|
+
return;
|
|
59
|
+
finalized = true;
|
|
58
60
|
const finalTangents = lastTangents;
|
|
59
|
-
|
|
61
|
+
releaseHistory();
|
|
60
62
|
useScene.getState().updateNode(fenceId, { tangents: finalTangents });
|
|
61
63
|
useLiveNodeOverrides.getState().clear(fenceId);
|
|
62
64
|
useScene.getState().markDirty(fenceId);
|
|
@@ -65,8 +67,11 @@ export const MoveFenceTangentTool = ({ target }) => {
|
|
|
65
67
|
event.nativeEvent?.stopPropagation?.();
|
|
66
68
|
};
|
|
67
69
|
const onCancel = () => {
|
|
70
|
+
if (finalized)
|
|
71
|
+
return;
|
|
68
72
|
restore();
|
|
69
|
-
|
|
73
|
+
finalized = true;
|
|
74
|
+
releaseHistory();
|
|
70
75
|
markToolCancelConsumed();
|
|
71
76
|
exit(false);
|
|
72
77
|
};
|
|
@@ -74,9 +79,9 @@ export const MoveFenceTangentTool = ({ target }) => {
|
|
|
74
79
|
emitter.on('grid:click', onGridClick);
|
|
75
80
|
emitter.on('tool:cancel', onCancel);
|
|
76
81
|
return () => {
|
|
77
|
-
if (!
|
|
82
|
+
if (!finalized) {
|
|
78
83
|
restore();
|
|
79
|
-
|
|
84
|
+
releaseHistory();
|
|
80
85
|
}
|
|
81
86
|
emitter.off('grid:move', onGridMove);
|
|
82
87
|
emitter.off('grid:click', onGridClick);
|
package/dist/fence/tool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/fence/tool.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/fence/tool.tsx"],"names":[],"mappings":"AA6bA,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAM7B,CAAA;AA0eD,eAAe,SAAS,CAAA"}
|
package/dist/fence/tool.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CursorSphere, clearPlacementSurface, createFenceOnCurrentLevel, createS
|
|
|
5
5
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { useThree } from '@react-three/fiber';
|
|
7
7
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { BufferGeometry, DoubleSide, Vector3 } from 'three';
|
|
9
9
|
import { DraftAngleArc, DraftAxisGuides, DraftMeasurementLabel, getNearestAxisAngleLabel, } from '../shared/draft-axis-guides';
|
|
10
10
|
const FENCE_PREVIEW_HEIGHT = 1.8;
|
|
11
11
|
const FENCE_PREVIEW_THICKNESS = 0.08;
|
|
@@ -216,14 +216,14 @@ function getDraftAngleLabels(start, end, segments, baseY, previewHeight, preview
|
|
|
216
216
|
}
|
|
217
217
|
return labels;
|
|
218
218
|
}
|
|
219
|
-
function getDraftMeasurementState(start, end, segments, unit, baseY, previewHeight, previewThickness) {
|
|
219
|
+
function getDraftMeasurementState(start, end, segments, unit, metricNotation, baseY, previewHeight, previewThickness) {
|
|
220
220
|
const dx = end[0] - start[0];
|
|
221
221
|
const dz = end[1] - start[1];
|
|
222
222
|
const length = Math.hypot(dx, dz);
|
|
223
223
|
if (length < 0.01)
|
|
224
224
|
return null;
|
|
225
225
|
return {
|
|
226
|
-
lengthLabel: formatLinearMeasurement(length, unit),
|
|
226
|
+
lengthLabel: formatLinearMeasurement(length, unit, metricNotation),
|
|
227
227
|
lengthPosition: [
|
|
228
228
|
(start[0] + end[0]) / 2,
|
|
229
229
|
baseY + previewHeight + DRAFT_LABEL_Y_OFFSET,
|
|
@@ -259,14 +259,10 @@ function updateFencePreview(mesh, start, end, previewHeight, previewThickness) {
|
|
|
259
259
|
}
|
|
260
260
|
mesh.visible = true;
|
|
261
261
|
direction.normalize();
|
|
262
|
-
const geometry = new BoxGeometry(length, previewHeight, previewThickness);
|
|
263
262
|
const angle = Math.atan2(direction.z, direction.x);
|
|
264
263
|
mesh.position.set((start.x + end.x) / 2, start.y + previewHeight / 2, (start.z + end.z) / 2);
|
|
265
264
|
mesh.rotation.y = -angle;
|
|
266
|
-
|
|
267
|
-
mesh.geometry.dispose();
|
|
268
|
-
}
|
|
269
|
-
mesh.geometry = geometry;
|
|
265
|
+
mesh.scale.set(length, previewHeight, previewThickness);
|
|
270
266
|
}
|
|
271
267
|
function getCurrentLevelElements() {
|
|
272
268
|
const currentLevelId = useViewer.getState().selection.levelId;
|
|
@@ -291,6 +287,7 @@ export const FenceTool = () => {
|
|
|
291
287
|
};
|
|
292
288
|
const StraightFenceTool = () => {
|
|
293
289
|
const unit = useViewer((state) => state.unit);
|
|
290
|
+
const metricNotation = useViewer((state) => state.metricNotation);
|
|
294
291
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
|
|
295
292
|
// A placed preset seeds `toolDefaults.fence` before the tool mounts, so
|
|
296
293
|
// the draft preview is drawn at the preset's height / thickness rather
|
|
@@ -406,7 +403,7 @@ const StraightFenceTool = () => {
|
|
|
406
403
|
}
|
|
407
404
|
previousFenceEnd = currentFenceEnd;
|
|
408
405
|
updateFencePreview(previewRef.current, startingPoint.current, endingPoint.current, previewHeightRef.current, previewThicknessRef.current);
|
|
409
|
-
setDraftMeasurement(getDraftMeasurementState([startingPoint.current.x, startingPoint.current.z], snappedLocal, getReferenceSegments(walls, fences), unit, startingPoint.current.y, previewHeightRef.current, previewThicknessRef.current));
|
|
406
|
+
setDraftMeasurement(getDraftMeasurementState([startingPoint.current.x, startingPoint.current.z], snappedLocal, getReferenceSegments(walls, fences), unit, metricNotation, startingPoint.current.y, previewHeightRef.current, previewThicknessRef.current));
|
|
410
407
|
}
|
|
411
408
|
else {
|
|
412
409
|
const snappedPoint = alignPoint(snapFenceDraftPoint({
|
|
@@ -523,8 +520,8 @@ const StraightFenceTool = () => {
|
|
|
523
520
|
draftPreview.setFenceDraftStart(null);
|
|
524
521
|
draftPreview.setFenceDraftEnd(null);
|
|
525
522
|
};
|
|
526
|
-
}, [unit]);
|
|
527
|
-
return (_jsxs("group", { children: [_jsx(DraftAxisGuides, { guide: axisGuide, labelColor: measurementColor, labelShadowColor: measurementShadowColor }), _jsx(CursorSphere, { height: previewHeight, ref: cursorRef }), _jsxs("mesh", { layers: EDITOR_LAYER, ref: previewRef, renderOrder: 1, visible: false, children: [_jsx("
|
|
523
|
+
}, [unit, metricNotation]);
|
|
524
|
+
return (_jsxs("group", { children: [_jsx(DraftAxisGuides, { guide: axisGuide, labelColor: measurementColor, labelShadowColor: measurementShadowColor }), _jsx(CursorSphere, { height: previewHeight, ref: cursorRef }), _jsxs("mesh", { layers: EDITOR_LAYER, ref: previewRef, renderOrder: 1, visible: false, children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: "#ffffff", depthTest: false, depthWrite: false, opacity: 0.45, side: DoubleSide, transparent: true })] }), draftMeasurement && (_jsxs(_Fragment, { children: [_jsx(DraftMeasurementLabel, { color: measurementColor, label: draftMeasurement.lengthLabel, position: draftMeasurement.lengthPosition, shadowColor: measurementShadowColor }), draftMeasurement.angleLabels.map((angleLabel) => (_jsxs("group", { children: [_jsx(DraftAngleArc, { arc: angleLabel.arc, color: measurementColor }), _jsx(DraftMeasurementLabel, { color: measurementColor, label: angleLabel.label, position: angleLabel.position, shadowColor: measurementShadowColor })] }, angleLabel.id)))] }))] }));
|
|
528
525
|
};
|
|
529
526
|
const SPLINE_PREVIEW_COLOR = '#8381ed';
|
|
530
527
|
const SPLINE_PREVIEW_SEGMENTS = 40;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/measurement/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EAWrB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAML,KAAK,QAAQ,EAKd,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/measurement/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EAWrB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAML,KAAK,QAAQ,EAKd,MAAM,OAAO,CAAA;AAiRd,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,CAO/E;AAED,eAAO,MAAM,mBAAmB,GAAI,UAAU;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,gCA0ItE,CAAA;AAED,eAAe,mBAAmB,CAAA"}
|
|
@@ -197,9 +197,9 @@ function buildRenderData(measurement) {
|
|
|
197
197
|
markerPoints,
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
|
-
function formatMeasurement(measurement, unit) {
|
|
200
|
+
function formatMeasurement(measurement, unit, metricNotation) {
|
|
201
201
|
if (measurement.kind === 'distance') {
|
|
202
|
-
return formatLinearMeasurement(measurementDistance(...measurement.points), unit);
|
|
202
|
+
return formatLinearMeasurement(measurementDistance(...measurement.points), unit, metricNotation);
|
|
203
203
|
}
|
|
204
204
|
if (measurement.kind === 'angle') {
|
|
205
205
|
return formatAngleRadians(measurementAngle(...measurement.points));
|
|
@@ -208,7 +208,7 @@ function formatMeasurement(measurement, unit) {
|
|
|
208
208
|
return `A ${formatAreaLabel(measurementArea(measurement.base), unit)}`;
|
|
209
209
|
}
|
|
210
210
|
if (measurement.kind === 'perimeter') {
|
|
211
|
-
return `P ${formatLinearMeasurement(measurementPerimeter(measurement.base), unit)}`;
|
|
211
|
+
return `P ${formatLinearMeasurement(measurementPerimeter(measurement.base), unit, metricNotation)}`;
|
|
212
212
|
}
|
|
213
213
|
return `V ${formatVolumeLabel(measurementPrismVolume(measurement.base, measurement.extrusion), unit)}`;
|
|
214
214
|
}
|
|
@@ -229,6 +229,7 @@ export const MeasurementRenderer = ({ node }) => {
|
|
|
229
229
|
const handlers = useNodeEvents(node, 'measurement');
|
|
230
230
|
const showMeasurements = useViewer((state) => state.showMeasurements);
|
|
231
231
|
const unit = useViewer((state) => state.unit);
|
|
232
|
+
const metricNotation = useViewer((state) => state.metricNotation);
|
|
232
233
|
const active = useViewer((state) => state.hoveredId === node.id || state.selection.selectedIds.some((id) => id === node.id));
|
|
233
234
|
const ownOverride = useLiveNodeOverrides((state) => state.overrides.get(node.id));
|
|
234
235
|
const effectiveNode = useMemo(() => (ownOverride ? { ...node, ...ownOverride } : node), [node, ownOverride]);
|
|
@@ -244,9 +245,9 @@ export const MeasurementRenderer = ({ node }) => {
|
|
|
244
245
|
});
|
|
245
246
|
const data = buildRenderData(resolved.payload);
|
|
246
247
|
const label = useMemo(() => {
|
|
247
|
-
const value = formatMeasurement(resolved.payload, unit);
|
|
248
|
+
const value = formatMeasurement(resolved.payload, unit, metricNotation);
|
|
248
249
|
return resolved.dangling.length > 0 ? `Unlinked · ${value}` : value;
|
|
249
|
-
}, [resolved, unit]);
|
|
250
|
+
}, [metricNotation, resolved, unit]);
|
|
250
251
|
const color = measurementPresentationColor(resolved.dangling.length > 0, active);
|
|
251
252
|
const lineMaterial = useMemo(() => new LineBasicNodeMaterial({
|
|
252
253
|
color,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { measurementDistance, measurementFeatureLength, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
|
-
import {
|
|
2
|
+
import { setSurfaceRaycastLayers, ZONE_LAYER } from '@pascal-app/viewer';
|
|
3
3
|
import { Matrix3, Matrix4, Quaternion, Raycaster, Vector2, Vector3, } from 'three';
|
|
4
4
|
import { matchMeasurementFeatureForNode } from './resolve';
|
|
5
5
|
const AXIS_SNAP_DISTANCE_PX = 16;
|
|
@@ -405,7 +405,7 @@ function collectMeasurementAxisSurfaceIntersections(context, levelObject, anchor
|
|
|
405
405
|
const origin = levelObject.localToWorld(new Vector3(...anchor));
|
|
406
406
|
const levelRotation = levelObject.getWorldQuaternion(new Quaternion());
|
|
407
407
|
const inverseLevelRotation = levelRotation.clone().invert();
|
|
408
|
-
raycaster.layers
|
|
408
|
+
setSurfaceRaycastLayers(raycaster.layers);
|
|
409
409
|
raycaster.near = 0;
|
|
410
410
|
raycaster.far = maxDistance;
|
|
411
411
|
const intersections = [];
|
|
@@ -452,9 +452,9 @@ export function createMeasurementSurfaceQuerySession(scene, options = {}) {
|
|
|
452
452
|
const verificationRaycaster = new Raycaster();
|
|
453
453
|
const axisRaycaster = new Raycaster();
|
|
454
454
|
const pointer = new Vector2();
|
|
455
|
-
pointerRaycaster.layers
|
|
456
|
-
verificationRaycaster.layers
|
|
457
|
-
axisRaycaster.layers
|
|
455
|
+
setSurfaceRaycastLayers(pointerRaycaster.layers);
|
|
456
|
+
setSurfaceRaycastLayers(verificationRaycaster.layers);
|
|
457
|
+
setSurfaceRaycastLayers(axisRaycaster.layers);
|
|
458
458
|
if (options.includeZoneLayer)
|
|
459
459
|
pointerRaycaster.layers.enable(ZONE_LAYER);
|
|
460
460
|
let context = null;
|