@pascal-app/editor 1.0.0 → 1.0.3
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/package.json +12 -6
- package/src/components/editor/bake-exporter.tsx +4 -0
- package/src/components/editor/bake-thumbnail.tsx +4 -1
- package/src/components/editor/custom-camera-controls.tsx +22 -74
- package/src/components/editor/editor-layout-mobile.tsx +1 -0
- package/src/components/editor/floating-action-menu.tsx +14 -1
- package/src/components/editor/floorplan-panel.tsx +233 -166
- package/src/components/editor/grid.tsx +31 -7
- package/src/components/editor/group-actions.ts +5 -9
- package/src/components/editor/group-floating-action-menu.tsx +19 -9
- package/src/components/editor/group-move-3d.ts +4 -6
- package/src/components/editor/group-rotate-handle.tsx +81 -35
- package/src/components/editor/group-selection-box-3d.tsx +25 -17
- package/src/components/editor/group-transform-shared.test.ts +126 -1
- package/src/components/editor/group-transform-shared.ts +79 -73
- package/src/components/editor/handles/handle-arrow.tsx +72 -2
- package/src/components/editor/handles/handle-placement.test.ts +111 -0
- package/src/components/editor/handles/handle-placement.ts +69 -0
- package/src/components/editor/handles/linear-resize-drag.ts +13 -2
- package/src/components/editor/handles/use-handle-drag.ts +80 -8
- package/src/components/editor/index.tsx +36 -13
- package/src/components/editor/node-action-menu.tsx +2 -0
- package/src/components/editor/node-arrow-handles.tsx +162 -69
- package/src/components/editor/registry-action-contributions.tsx +53 -0
- package/src/components/editor/selection-manager.tsx +49 -0
- package/src/components/editor/thumbnail-generator.tsx +11 -2
- package/src/components/editor/wall-move-side-handles.tsx +71 -21
- package/src/components/editor/wall-opening-highlights.tsx +2 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +12 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +154 -49
- package/src/components/editor-2d/renderers/floorplan-draft-wall-measurement.tsx +166 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +49 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +113 -24
- package/src/components/procedural-items/inspector.tsx +223 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +3 -1
- package/src/components/systems/zone/zone-system.tsx +8 -4
- package/src/components/tools/__fixtures__/hosting.ts +10 -0
- package/src/components/tools/item/item-surface-placement.test.ts +169 -0
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +204 -0
- package/src/components/tools/item/placement-strategies.ts +212 -200
- package/src/components/tools/item/placement-types.ts +4 -0
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +257 -0
- package/src/components/tools/item/use-draft-node.ts +136 -47
- package/src/components/tools/item/use-placement-coordinator.tsx +391 -157
- package/src/components/tools/registry/item-surface-child-frame.test.ts +90 -0
- package/src/components/tools/registry/item-surface-move.test.ts +509 -0
- package/src/components/tools/registry/item-surface-move.ts +463 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +257 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +237 -78
- package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
- package/src/components/tools/shared/affordance-dispatch.ts +13 -0
- package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
- package/src/components/tools/shared/pointer-support-cap.ts +27 -13
- package/src/components/tools/shared/shelf-stickiness.ts +54 -0
- package/src/components/tools/shared/surface-hit.test.ts +56 -0
- package/src/components/tools/shared/surface-hit.ts +23 -0
- package/src/components/tools/shared/surface-rejection.test.tsx +107 -0
- package/src/components/tools/shared/surface-rejection.tsx +43 -0
- package/src/components/tools/tool-manager.tsx +36 -3
- package/src/components/tools/zone/zone-tool.tsx +13 -4
- package/src/components/ui/command-palette/editor-commands.tsx +54 -1
- package/src/components/ui/controls/material-paint-panel.tsx +6 -67
- package/src/components/ui/controls/material-picker.tsx +5 -67
- package/src/components/ui/controls/metric-control.tsx +29 -13
- package/src/components/ui/controls/slider-control.tsx +87 -35
- package/src/components/ui/floating-level-selector.tsx +81 -5
- package/src/components/ui/helpers/helper-manager.tsx +24 -6
- package/src/components/ui/helpers/registered-tool-helper.tsx +2 -32
- package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
- package/src/components/ui/panels/multi-height-mode.tsx +27 -21
- package/src/components/ui/panels/multi-parametric-inspector.tsx +6 -3
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +9 -17
- package/src/components/ui/panels/reference-panel.tsx +3 -3
- package/src/components/ui/primitives/checkbox.tsx +34 -0
- package/src/components/ui/primitives/shortcut-token.tsx +4 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +669 -336
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +30 -11
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +8 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +106 -4
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +2 -7
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +3 -8
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +6 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +224 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +22 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +24 -7
- package/src/components/ui/snap-target-badge.tsx +2 -0
- package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
- package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
- package/src/components/viewer/floorplan-preview.tsx +1 -12
- package/src/components/viewer/viewer-scene-header.tsx +3 -0
- package/src/components/viewer/viewer-units-panel.tsx +116 -0
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.integration.test.tsx +197 -0
- package/src/hooks/use-auto-save.ts +21 -6
- package/src/hooks/use-ceiling-events.test.ts +25 -2
- package/src/hooks/use-ceiling-events.ts +7 -1
- package/src/hooks/use-grid-events.test.ts +2 -0
- package/src/hooks/use-grid-events.ts +16 -11
- package/src/hooks/use-keyboard.test.ts +7 -0
- package/src/hooks/use-keyboard.ts +43 -28
- package/src/index.tsx +68 -3
- package/src/lib/catalog-panel-model.ts +39 -0
- package/src/lib/catalog-selection.test.ts +27 -0
- package/src/lib/catalog-selection.ts +8 -0
- package/src/lib/contextual-help.test.ts +27 -1
- package/src/lib/contextual-help.ts +16 -9
- package/src/lib/export-texture-utils.test.ts +167 -0
- package/src/lib/export-texture-utils.ts +121 -0
- package/src/lib/export-user-data.test.ts +48 -0
- package/src/lib/export-user-data.ts +69 -0
- package/src/lib/floorplan/floorplan-export.test.ts +142 -2
- package/src/lib/floorplan/floorplan-export.tsx +9 -3
- package/src/lib/floorplan/floorplan-extension.ts +16 -0
- package/src/lib/floorplan/floorplan-readonly.ts +4 -0
- package/src/lib/fresh-planar-placement.ts +173 -33
- package/src/lib/glb-export-serialize.test.ts +280 -0
- package/src/lib/glb-export.test.ts +265 -16
- package/src/lib/glb-export.ts +197 -37
- package/src/lib/grid-frame.test.ts +19 -0
- package/src/lib/grid-frame.ts +18 -0
- package/src/lib/interaction/scope.ts +9 -1
- package/src/lib/keyboard-pan.test.ts +62 -0
- package/src/lib/keyboard-pan.ts +89 -0
- package/src/lib/level-duplication.test.ts +73 -0
- package/src/lib/level-duplication.ts +32 -2
- package/src/lib/linear-display.test.ts +59 -0
- package/src/lib/linear-display.ts +39 -0
- package/src/lib/local-guide-image.test.ts +24 -0
- package/src/lib/local-guide-image.ts +34 -1
- package/src/lib/material-catalog-model.ts +72 -0
- package/src/lib/material-paint-panel-model.ts +76 -0
- package/src/lib/node-action-movement.ts +21 -0
- package/src/lib/overlay-transform.test.ts +28 -0
- package/src/lib/overlay-transform.ts +12 -0
- package/src/lib/panel-rows.ts +75 -0
- package/src/lib/panel-tool-options.test.ts +34 -0
- package/src/lib/panel-tool-options.ts +46 -0
- package/src/lib/parametric-node-update.ts +37 -0
- package/src/lib/portable-export.test.ts +89 -0
- package/src/lib/portable-export.ts +84 -11
- package/src/lib/print-shell-compiler-manifold-core.ts +59 -10
- package/src/lib/print-shell-compiler-manifold-worker.ts +14 -1
- package/src/lib/print-shell-compiler-manifold.worker.ts +1 -1
- package/src/lib/print-shell-compiler-protocol.ts +8 -0
- package/src/lib/scene-clipboard.ts +1 -1
- package/src/lib/scene.test.ts +99 -0
- package/src/lib/scene.ts +21 -3
- package/src/lib/send-to-app.test.ts +108 -0
- package/src/lib/send-to-app.ts +121 -0
- package/src/lib/sfx/movement-tick.ts +37 -0
- package/src/lib/snapping-mode.test.ts +13 -2
- package/src/lib/snapping-mode.ts +11 -6
- package/src/lib/spatial-drag-plane.test.ts +53 -0
- package/src/lib/spatial-drag-plane.ts +19 -0
- package/src/lib/spatial-pointer-input.test.ts +72 -0
- package/src/lib/spatial-pointer-input.ts +62 -0
- package/src/lib/spatial-snap.test.ts +47 -0
- package/src/lib/surface-attachment.ts +77 -0
- package/src/lib/terrain-panel-model.ts +117 -0
- package/src/lib/units.ts +254 -0
- package/src/lib/usdz-export.ts +43 -16
- package/src/lib/use-linear-display.ts +8 -32
- package/src/lib/world-grid-snap.test.ts +48 -0
- package/src/lib/world-grid-snap.ts +9 -5
- package/src/store/rotation-snapping.test.ts +53 -0
- package/src/store/use-editor.tsx +10 -2
- package/src/store/use-floorplan-draft-preview.ts +10 -1
- package/src/store/use-handle-group.ts +4 -0
- package/src/store/use-interaction-scope.ts +155 -6
- package/src/store/use-send-to-app.ts +21 -0
package/src/index.tsx
CHANGED
|
@@ -49,7 +49,7 @@ export { FloatingActionMenu as FloatingMenu } from './components/editor/floating
|
|
|
49
49
|
// camera controls via the `useViewer.inputDragging` / `useEditor.movingNode`
|
|
50
50
|
// flags. Tools place onto `useViewer.selection.levelId`, so the host must set a
|
|
51
51
|
// building + level selection first.
|
|
52
|
-
export { Grid } from './components/editor/grid'
|
|
52
|
+
export { EDITOR_GRID_INPUT_NAME, Grid } from './components/editor/grid'
|
|
53
53
|
export {
|
|
54
54
|
DimensionPill,
|
|
55
55
|
type DimensionPillPart,
|
|
@@ -79,12 +79,18 @@ export {
|
|
|
79
79
|
useInvisibleHitAreaMaterial,
|
|
80
80
|
} from './components/editor/node-arrow-handles'
|
|
81
81
|
export { QuickMeasurementCard } from './components/editor/quick-measurement-card'
|
|
82
|
+
export { SelectionManager } from './components/editor/selection-manager'
|
|
82
83
|
export {
|
|
83
84
|
type SnapshotCameraData,
|
|
84
85
|
ThumbnailGenerator,
|
|
85
86
|
} from './components/editor/thumbnail-generator'
|
|
87
|
+
export { WallMoveSideHandles } from './components/editor/wall-move-side-handles'
|
|
86
88
|
export { useFloorplanRender } from './components/editor-2d/floorplan-render-context'
|
|
87
89
|
export { FloorplanDimensionRenderer } from './components/editor-2d/renderers/floorplan-dimension-renderer'
|
|
90
|
+
export {
|
|
91
|
+
type DraftWallMeasurement,
|
|
92
|
+
FloorplanDraftWallMeasurement,
|
|
93
|
+
} from './components/editor-2d/renderers/floorplan-draft-wall-measurement'
|
|
88
94
|
export { FloorplanGeometryRenderer } from './components/editor-2d/renderers/floorplan-geometry-renderer'
|
|
89
95
|
export {
|
|
90
96
|
FloorplanNodePreview,
|
|
@@ -137,8 +143,13 @@ export {
|
|
|
137
143
|
type PlacementCoordinatorConfig,
|
|
138
144
|
usePlacementCoordinator,
|
|
139
145
|
} from './components/tools/item/use-placement-coordinator'
|
|
146
|
+
export { MoveRegistryNodeTool } from './components/tools/registry/move-registry-node-tool'
|
|
140
147
|
export { useRegistryToolContext } from './components/tools/registry-tool-context'
|
|
141
148
|
export { CursorSphere } from './components/tools/shared/cursor-sphere'
|
|
149
|
+
export {
|
|
150
|
+
DRAFT_LABEL_Y_OFFSET,
|
|
151
|
+
DraftMeasurementLabel,
|
|
152
|
+
} from './components/tools/shared/draft-measurement-label'
|
|
142
153
|
export { DragBoundingBox } from './components/tools/shared/drag-bounding-box'
|
|
143
154
|
export { getFloorStackPreviewPosition } from './components/tools/shared/floor-stack-preview'
|
|
144
155
|
export { useFreshPlacementVisibility } from './components/tools/shared/fresh-placement-visibility'
|
|
@@ -220,6 +231,7 @@ export {
|
|
|
220
231
|
CameraActions as ToolbarRight,
|
|
221
232
|
CameraActions as ViewerToolbarRight,
|
|
222
233
|
} from './components/ui/action-menu/camera-actions'
|
|
234
|
+
export { furnishTools } from './components/ui/action-menu/furnish-tools'
|
|
223
235
|
export {
|
|
224
236
|
ViewToggles as ToolbarLeft,
|
|
225
237
|
ViewToggles as ViewerToolbarLeft,
|
|
@@ -246,6 +258,19 @@ export { FloatingLevelSelector } from './components/ui/floating-level-selector'
|
|
|
246
258
|
export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items'
|
|
247
259
|
// Item collections UI — used by the kind-owned ItemPanel in nodes/.
|
|
248
260
|
export { CollectionsPopover } from './components/ui/panels/collections/collections-popover'
|
|
261
|
+
export {
|
|
262
|
+
resolveHomogeneousSelection,
|
|
263
|
+
resolveUniqueSelectionIds,
|
|
264
|
+
} from './components/ui/panels/homogeneous-selection'
|
|
265
|
+
export {
|
|
266
|
+
commitMultiNodeFields,
|
|
267
|
+
fieldVisibleForAll,
|
|
268
|
+
firstNumericFieldValue,
|
|
269
|
+
firstVec3FieldValue,
|
|
270
|
+
reduceFieldValue,
|
|
271
|
+
reduceHeightBoundMode,
|
|
272
|
+
} from './components/ui/panels/multi-field-value'
|
|
273
|
+
export { applyMultiHeightMode } from './components/ui/panels/multi-height-mode'
|
|
249
274
|
// Phase 5 Stage E — kinds with bespoke editors (slab holes list,
|
|
250
275
|
// ceiling height presets, etc.) use `parametrics.customPanel` to mount
|
|
251
276
|
// a kind-owned panel and need PanelWrapper for the chrome.
|
|
@@ -268,6 +293,7 @@ export {
|
|
|
268
293
|
} from './components/ui/primitives/shortcut-token'
|
|
269
294
|
export { useSidebarStore } from './components/ui/primitives/sidebar'
|
|
270
295
|
export { Slider } from './components/ui/primitives/slider'
|
|
296
|
+
export { Tooltip, TooltipContent, TooltipTrigger } from './components/ui/primitives/tooltip'
|
|
271
297
|
export { SceneLoader } from './components/ui/scene-loader'
|
|
272
298
|
export type { ExtraPanel } from './components/ui/sidebar/icon-rail'
|
|
273
299
|
export { ItemsPanel } from './components/ui/sidebar/panels/items-panel'
|
|
@@ -317,6 +343,7 @@ export {
|
|
|
317
343
|
ViewerStageSwitcher,
|
|
318
344
|
type ViewerStageSwitcherProps,
|
|
319
345
|
} from './components/viewer/viewer-stage-switcher'
|
|
346
|
+
export { ViewerUnitsPanel } from './components/viewer/viewer-units-panel'
|
|
320
347
|
export {
|
|
321
348
|
WalkthroughHud,
|
|
322
349
|
type WalkthroughHudProps,
|
|
@@ -328,7 +355,7 @@ export type { SaveStatus } from './hooks/use-auto-save'
|
|
|
328
355
|
// can express their affordances declaratively in their own folder.
|
|
329
356
|
export { type UseDragActionArgs, useDragAction } from './hooks/use-drag-action'
|
|
330
357
|
// Phase 5 Stage D — extras for kind-owned placement tools (FenceTool etc.).
|
|
331
|
-
export { markToolCancelConsumed } from './hooks/use-keyboard'
|
|
358
|
+
export { cancelActiveTool, markToolCancelConsumed } from './hooks/use-keyboard'
|
|
332
359
|
export { useReducedMotion } from './hooks/use-reduced-motion'
|
|
333
360
|
export { type Selection, useSelection } from './hooks/use-selection'
|
|
334
361
|
export {
|
|
@@ -337,6 +364,11 @@ export {
|
|
|
337
364
|
type PlacementSurface,
|
|
338
365
|
publishPlacementSurface,
|
|
339
366
|
} from './lib/active-placement-surface'
|
|
367
|
+
export {
|
|
368
|
+
activateCatalogItem,
|
|
369
|
+
filterCatalogItems,
|
|
370
|
+
isCatalogItemSelected,
|
|
371
|
+
} from './lib/catalog-panel-model'
|
|
340
372
|
export {
|
|
341
373
|
CEILING_ALIGNMENT_THRESHOLD_M,
|
|
342
374
|
type CeilingPlanSnapInput,
|
|
@@ -359,6 +391,7 @@ export {
|
|
|
359
391
|
continuationContextOf,
|
|
360
392
|
nextContinuation,
|
|
361
393
|
} from './lib/continuation'
|
|
394
|
+
export { canDirectMoveNode } from './lib/direct-manipulation'
|
|
362
395
|
export { createEditorApi } from './lib/editor-api'
|
|
363
396
|
export {
|
|
364
397
|
clearStructuralElevationGuide,
|
|
@@ -378,6 +411,7 @@ export {
|
|
|
378
411
|
resolveElevatorSupportLevelId,
|
|
379
412
|
resolveElevatorSupportY,
|
|
380
413
|
} from './lib/elevator-support'
|
|
414
|
+
export type { ExportTextureUtils } from './lib/export-texture-utils'
|
|
381
415
|
export { getFloatingMenuScale } from './lib/floating-menu-scale'
|
|
382
416
|
// Floor-plan stair helpers — the cumulative-transform walk
|
|
383
417
|
// (`computeFloorplanStairSegmentTransforms`) and the rich segment-entry
|
|
@@ -432,6 +466,7 @@ export {
|
|
|
432
466
|
type FloorplanMode,
|
|
433
467
|
isFloorplanToolAvailableInMode,
|
|
434
468
|
} from './lib/floorplan/floorplan-mode'
|
|
469
|
+
export { clientToPlan } from './lib/floorplan/plan-coords'
|
|
435
470
|
export {
|
|
436
471
|
commitFreshPlacementSubtree,
|
|
437
472
|
createFreshPlacementSubtree,
|
|
@@ -471,6 +506,8 @@ export {
|
|
|
471
506
|
movingNodeOf,
|
|
472
507
|
scopeNodeId,
|
|
473
508
|
} from './lib/interaction/scope'
|
|
509
|
+
export { isEditableKeyboardTarget } from './lib/keyboard-pan'
|
|
510
|
+
export { useMaterialCatalogModel } from './lib/material-catalog-model'
|
|
474
511
|
export {
|
|
475
512
|
type ActivePaintMaterial,
|
|
476
513
|
buildResetSurfaceMaterialUpdates,
|
|
@@ -480,6 +517,7 @@ export {
|
|
|
480
517
|
getActivePaintMaterialLabel,
|
|
481
518
|
hasActivePaintMaterial,
|
|
482
519
|
} from './lib/material-paint'
|
|
520
|
+
export { useMaterialPaintPanelModel } from './lib/material-paint-panel-model'
|
|
483
521
|
export {
|
|
484
522
|
CREATABLE_MEASUREMENT_KINDS,
|
|
485
523
|
type CreatableMeasurementKind,
|
|
@@ -526,6 +564,21 @@ export type {
|
|
|
526
564
|
ModelExportFormat,
|
|
527
565
|
ModelExportOptions,
|
|
528
566
|
} from './lib/model-export'
|
|
567
|
+
export {
|
|
568
|
+
cyclePaintScope,
|
|
569
|
+
type PaintHoverInfo,
|
|
570
|
+
type PaintScope,
|
|
571
|
+
paintScopeLabel,
|
|
572
|
+
} from './lib/paint-scope'
|
|
573
|
+
export {
|
|
574
|
+
getNodePanelModel,
|
|
575
|
+
type NodePanelModel,
|
|
576
|
+
PANEL_MODEL_EXTENSION,
|
|
577
|
+
type PanelAction,
|
|
578
|
+
type PanelRow,
|
|
579
|
+
} from './lib/panel-rows'
|
|
580
|
+
export { type PanelToolOption, usePanelToolHints } from './lib/panel-tool-options'
|
|
581
|
+
export { commitParametricNodeFields } from './lib/parametric-node-update'
|
|
529
582
|
export { consumePlacementDragRelease } from './lib/placement-drag-release'
|
|
530
583
|
export {
|
|
531
584
|
addFreshPlacementMetadata,
|
|
@@ -544,6 +597,8 @@ export {
|
|
|
544
597
|
editorHostPanelRegistry,
|
|
545
598
|
registerEditorHostPanel,
|
|
546
599
|
} from './lib/plugin-panels'
|
|
600
|
+
export { configureManifoldRuntime } from './lib/print-shell-compiler-manifold-worker'
|
|
601
|
+
export type { ManifoldRuntimeOptions } from './lib/print-shell-compiler-protocol'
|
|
547
602
|
export {
|
|
548
603
|
createQuickMeasurementPointerScheduler,
|
|
549
604
|
quickMeasurementContext,
|
|
@@ -557,7 +612,7 @@ export { hasRoofFaceChildOverlap, type RoofWallHit, resolveRoofWallHit } from '.
|
|
|
557
612
|
export type { SceneGraph } from './lib/scene'
|
|
558
613
|
export { applySceneGraphToEditor } from './lib/scene'
|
|
559
614
|
export { movementSfxStepKey } from './lib/sfx/movement-tick'
|
|
560
|
-
export { triggerSFX } from './lib/sfx-bus'
|
|
615
|
+
export { emitDeleteSFX, triggerSFX } from './lib/sfx-bus'
|
|
561
616
|
export { playSFX, type SFXName, type SFXPlaybackOptions } from './lib/sfx-player'
|
|
562
617
|
export {
|
|
563
618
|
clearSlabSnapFeedback,
|
|
@@ -570,12 +625,14 @@ export {
|
|
|
570
625
|
type SlabPlanSnapResult,
|
|
571
626
|
} from './lib/slab-plan-snap'
|
|
572
627
|
export {
|
|
628
|
+
cycleSnappingModeIn,
|
|
573
629
|
getSnappingModeLabel,
|
|
574
630
|
resolveSnapFlags,
|
|
575
631
|
type SnapContext,
|
|
576
632
|
type SnapFlags,
|
|
577
633
|
type SnappingMode,
|
|
578
634
|
} from './lib/snapping-mode'
|
|
635
|
+
export { getSpatialPointerId, spatialPointerInput } from './lib/spatial-pointer-input'
|
|
579
636
|
export { duplicateStairSubtree } from './lib/stair-duplication'
|
|
580
637
|
export {
|
|
581
638
|
getBuildingLevelsForLevel,
|
|
@@ -585,6 +642,7 @@ export {
|
|
|
585
642
|
resolveStairPlacementLevelId,
|
|
586
643
|
resolveStairToLevelId,
|
|
587
644
|
} from './lib/stair-levels'
|
|
645
|
+
export { surfaceAttachmentId, surfaceFramePose, updateSurfaceNode } from './lib/surface-attachment'
|
|
588
646
|
export {
|
|
589
647
|
clearSurfacePlanSnapFeedback,
|
|
590
648
|
resolveSurfacePlanPointSnap,
|
|
@@ -592,14 +650,20 @@ export {
|
|
|
592
650
|
type SurfacePlanSnapInput,
|
|
593
651
|
type SurfacePlanSnapResult,
|
|
594
652
|
} from './lib/surface-plan-snap'
|
|
653
|
+
export { useTerrainPanelRows } from './lib/terrain-panel-model'
|
|
595
654
|
export {
|
|
655
|
+
brushRadiusRange,
|
|
656
|
+
clipTerrainPatchToSite,
|
|
657
|
+
commitStroke,
|
|
596
658
|
fieldExtentForSite,
|
|
597
659
|
flattenSite,
|
|
598
660
|
resetSiteTerrain,
|
|
599
661
|
resolveFlattenTarget,
|
|
600
662
|
sculptFieldForSite,
|
|
663
|
+
terrainPointInsideSite,
|
|
601
664
|
} from './lib/terrain-sculpt'
|
|
602
665
|
export { exportSceneToUsdz, type UsdzExportOptions } from './lib/usdz-export'
|
|
666
|
+
export { useLinearDisplay } from './lib/use-linear-display'
|
|
603
667
|
// `cn` (twMerge + clsx) — used by kind-owned panels in `@pascal-app/
|
|
604
668
|
// nodes` so they don't need their own copy / their own tailwind-merge
|
|
605
669
|
// dependency.
|
|
@@ -647,6 +711,7 @@ export {
|
|
|
647
711
|
isAngleSnapActive,
|
|
648
712
|
isGridSnapActive,
|
|
649
713
|
isMagneticSnapActive,
|
|
714
|
+
selectDefaultBuildingAndLevel,
|
|
650
715
|
} from './store/use-editor'
|
|
651
716
|
export { default as useFacingPose, type FacingPose } from './store/use-facing-pose'
|
|
652
717
|
export { default as useFenceCurveDraft } from './store/use-fence-curve-draft'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
2
|
+
import { CATALOG_ITEMS, type CatalogItem } from '../components/ui/item-catalog/catalog-items'
|
|
3
|
+
import useEditor, { type CatalogCategory } from '../store/use-editor'
|
|
4
|
+
|
|
5
|
+
export function filterCatalogItems({
|
|
6
|
+
category,
|
|
7
|
+
items = CATALOG_ITEMS,
|
|
8
|
+
search = '',
|
|
9
|
+
activePlacementTag,
|
|
10
|
+
activeFunctionalTag,
|
|
11
|
+
overrideItems,
|
|
12
|
+
}: {
|
|
13
|
+
category?: CatalogCategory
|
|
14
|
+
items?: readonly CatalogItem[]
|
|
15
|
+
search?: string
|
|
16
|
+
activePlacementTag?: string | null
|
|
17
|
+
activeFunctionalTag?: string | null
|
|
18
|
+
overrideItems?: readonly CatalogItem[]
|
|
19
|
+
}): readonly CatalogItem[] {
|
|
20
|
+
return (
|
|
21
|
+
overrideItems ??
|
|
22
|
+
items.filter((item) => {
|
|
23
|
+
if (!search && category && item.category !== category) return false
|
|
24
|
+
if (activePlacementTag && !item.tags?.includes(activePlacementTag)) return false
|
|
25
|
+
if (activeFunctionalTag && !item.tags?.includes(activeFunctionalTag)) return false
|
|
26
|
+
return !search || item.name.toLowerCase().includes(search.toLowerCase())
|
|
27
|
+
})
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function activateCatalogItem(item: CatalogItem) {
|
|
32
|
+
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
33
|
+
const editor = useEditor.getState()
|
|
34
|
+
editor.setSelectedItem(item)
|
|
35
|
+
editor.setTool(item.tool ?? 'item')
|
|
36
|
+
editor.setMode('build')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { isCatalogItemSelected } from './catalog-selection'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { test } from 'bun:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { isCatalogItemSelected } from './catalog-selection'
|
|
4
|
+
|
|
5
|
+
test('no selection leaves every tile unhighlighted, including items without model URLs', () => {
|
|
6
|
+
for (const item of [{ id: 'chair' }, { id: 'cabinet' }]) {
|
|
7
|
+
assert.equal(isCatalogItemSelected(item, null), false)
|
|
8
|
+
}
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('only the chosen item is highlighted when model URLs are missing or shared', () => {
|
|
12
|
+
for (const src of [undefined, '/shared-model.glb']) {
|
|
13
|
+
const items = [
|
|
14
|
+
{ id: 'first', src },
|
|
15
|
+
{ id: 'second', src },
|
|
16
|
+
]
|
|
17
|
+
assert.deepEqual(
|
|
18
|
+
items.map((item) => isCatalogItemSelected(item, items[1]!)),
|
|
19
|
+
[false, true],
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('selection follows identity across reloaded catalog objects', () => {
|
|
25
|
+
assert.equal(isCatalogItemSelected({ id: 'chair' }, { id: 'chair' }), true)
|
|
26
|
+
assert.equal(isCatalogItemSelected({ id: 'chair' }, { id: 'table' }), false)
|
|
27
|
+
})
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { resolveSelectModeHelpHints } from './contextual-help'
|
|
2
|
+
import { resolveRotateHandleHelpHints, resolveSelectModeHelpHints } from './contextual-help'
|
|
3
|
+
|
|
4
|
+
test('rotation advertises Alt as the temporary free-rotation override', () => {
|
|
5
|
+
expect(resolveRotateHandleHelpHints(false)).toEqual([
|
|
6
|
+
{ keys: ['Alt'], label: 'Hold to rotate freely', active: false },
|
|
7
|
+
])
|
|
8
|
+
expect(resolveRotateHandleHelpHints(true)).toEqual([
|
|
9
|
+
{ keys: ['Alt'], label: 'Rotating freely (no angle step)', active: true },
|
|
10
|
+
])
|
|
11
|
+
})
|
|
3
12
|
|
|
4
13
|
describe('resolveSelectModeHelpHints', () => {
|
|
5
14
|
test('stays hidden in idle select mode with no selection', () => {
|
|
@@ -117,4 +126,21 @@ describe('resolveSelectModeHelpHints', () => {
|
|
|
117
126
|
},
|
|
118
127
|
])
|
|
119
128
|
})
|
|
129
|
+
|
|
130
|
+
test('shows the single-side radius hint for rounded openings', () => {
|
|
131
|
+
expect(
|
|
132
|
+
resolveSelectModeHelpHints({
|
|
133
|
+
selectedCount: 1,
|
|
134
|
+
hasMovableSelection: true,
|
|
135
|
+
hasRotatableSelection: false,
|
|
136
|
+
hasOpeningRadiusSelection: true,
|
|
137
|
+
commandPressed: false,
|
|
138
|
+
shiftPressed: false,
|
|
139
|
+
}),
|
|
140
|
+
).toContainEqual({
|
|
141
|
+
keys: ['Shift'],
|
|
142
|
+
label: 'Adjust one side’s corner radius',
|
|
143
|
+
active: false,
|
|
144
|
+
})
|
|
145
|
+
})
|
|
120
146
|
})
|
|
@@ -11,8 +11,8 @@ export type ContextualShortcutHint = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// `activeHandleDrag.label` value a rotate gizmo sets while dragging, so the
|
|
14
|
-
// contextual HUD can surface the
|
|
15
|
-
//
|
|
14
|
+
// contextual HUD can surface the rotation snapping mode for the duration.
|
|
15
|
+
// Distinct from resize handles,
|
|
16
16
|
// which route their own measurement label here.
|
|
17
17
|
export const ROTATE_HANDLE_DRAG_LABEL = 'rotate-handle'
|
|
18
18
|
|
|
@@ -29,15 +29,12 @@ export const RESIZE_HANDLE_DRAG_LABEL = 'resize-handle'
|
|
|
29
29
|
export const GROUP_MOVE_DRAG_LABEL = 'group-move-handle'
|
|
30
30
|
export const GROUP_ROTATE_DRAG_LABEL = 'group-rotate-handle'
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
// (free rotation), the same toggle wall drafting exposes. `active` lights the
|
|
34
|
-
// pill while Shift is held.
|
|
35
|
-
export function resolveRotateHandleHelpHints(shiftPressed: boolean): ContextualShortcutHint[] {
|
|
32
|
+
export function resolveRotateHandleHelpHints(altPressed: boolean): ContextualShortcutHint[] {
|
|
36
33
|
return [
|
|
37
34
|
{
|
|
38
|
-
keys: [
|
|
39
|
-
label:
|
|
40
|
-
active:
|
|
35
|
+
keys: [ALT_KEY],
|
|
36
|
+
label: altPressed ? 'Rotating freely (no angle step)' : 'Hold to rotate freely',
|
|
37
|
+
active: altPressed,
|
|
41
38
|
},
|
|
42
39
|
]
|
|
43
40
|
}
|
|
@@ -46,6 +43,7 @@ export type SelectModeHelpContext = {
|
|
|
46
43
|
selectedCount: number
|
|
47
44
|
hasMovableSelection: boolean
|
|
48
45
|
hasRotatableSelection: boolean
|
|
46
|
+
hasOpeningRadiusSelection?: boolean
|
|
49
47
|
commandPressed: boolean
|
|
50
48
|
shiftPressed: boolean
|
|
51
49
|
// When a single MEP node is selected its in-world handle rig (click a dot to
|
|
@@ -67,6 +65,7 @@ export function resolveSelectModeHelpHints({
|
|
|
67
65
|
selectedCount,
|
|
68
66
|
hasMovableSelection,
|
|
69
67
|
hasRotatableSelection,
|
|
68
|
+
hasOpeningRadiusSelection = false,
|
|
70
69
|
commandPressed,
|
|
71
70
|
shiftPressed,
|
|
72
71
|
mepSelection = null,
|
|
@@ -142,6 +141,14 @@ export function resolveSelectModeHelpHints({
|
|
|
142
141
|
})
|
|
143
142
|
}
|
|
144
143
|
|
|
144
|
+
if (hasOpeningRadiusSelection) {
|
|
145
|
+
hints.push({
|
|
146
|
+
keys: [SHIFT_KEY],
|
|
147
|
+
label: 'Adjust one side’s corner radius',
|
|
148
|
+
active: shiftPressed,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
145
152
|
hints.push({
|
|
146
153
|
keys: [[COMMAND_KEY, SHIFT_KEY], LEFT_CLICK],
|
|
147
154
|
label: 'Add or remove objects from the selection',
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import type { WebGPURenderer } from 'three/webgpu'
|
|
4
|
+
import { createExportTextureUtils } from './export-texture-utils'
|
|
5
|
+
|
|
6
|
+
const globals = globalThis as unknown as { document?: unknown }
|
|
7
|
+
const previousDocument = globals.document
|
|
8
|
+
|
|
9
|
+
type FakeRenderer = {
|
|
10
|
+
renderer: WebGPURenderer
|
|
11
|
+
calls: { render: number; dispose: number; destroy: number; sizes: [number, number][] }
|
|
12
|
+
domElement: object
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fakeRenderer(): FakeRenderer {
|
|
16
|
+
const calls = { render: 0, dispose: 0, destroy: 0, sizes: [] as [number, number][] }
|
|
17
|
+
const domElement = { kind: 'renderer-canvas' }
|
|
18
|
+
const renderer = {
|
|
19
|
+
domElement,
|
|
20
|
+
outputColorSpace: THREE.SRGBColorSpace,
|
|
21
|
+
backend: {
|
|
22
|
+
device: {
|
|
23
|
+
destroy: () => {
|
|
24
|
+
calls.destroy += 1
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
setSize: (width: number, height: number) => {
|
|
29
|
+
calls.sizes.push([width, height])
|
|
30
|
+
},
|
|
31
|
+
render: () => {
|
|
32
|
+
calls.render += 1
|
|
33
|
+
},
|
|
34
|
+
dispose: async () => {
|
|
35
|
+
calls.dispose += 1
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
return { renderer: renderer as unknown as WebGPURenderer, calls, domElement }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function installCanvasDocument(drawn: object[]) {
|
|
42
|
+
globals.document = {
|
|
43
|
+
createElement: (tagName: string) => {
|
|
44
|
+
if (tagName !== 'canvas') throw new Error(`Unexpected element request: ${tagName}`)
|
|
45
|
+
return {
|
|
46
|
+
width: 0,
|
|
47
|
+
height: 0,
|
|
48
|
+
getContext: () => ({
|
|
49
|
+
drawImage: (source: object) => {
|
|
50
|
+
drawn.push(source)
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function compressedTexture(name: string, width = 8, height = 4): THREE.CompressedTexture {
|
|
59
|
+
const texture = new THREE.CompressedTexture([], width, height)
|
|
60
|
+
texture.name = name
|
|
61
|
+
texture.colorSpace = THREE.NoColorSpace
|
|
62
|
+
texture.wrapS = THREE.RepeatWrapping
|
|
63
|
+
texture.wrapT = THREE.MirroredRepeatWrapping
|
|
64
|
+
texture.minFilter = THREE.NearestFilter
|
|
65
|
+
texture.magFilter = THREE.NearestFilter
|
|
66
|
+
return texture
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
globals.document = previousDocument
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('createExportTextureUtils', () => {
|
|
74
|
+
test('starts one renderer for every compressed texture in the export and releases it once', async () => {
|
|
75
|
+
const drawn: object[] = []
|
|
76
|
+
installCanvasDocument(drawn)
|
|
77
|
+
const fake = fakeRenderer()
|
|
78
|
+
let starts = 0
|
|
79
|
+
const utils = createExportTextureUtils(async () => {
|
|
80
|
+
starts += 1
|
|
81
|
+
return fake.renderer
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const readable = await Promise.all([
|
|
85
|
+
utils.decompress(compressedTexture('NormalGL_a')),
|
|
86
|
+
utils.decompress(compressedTexture('Color_b', 16, 16)),
|
|
87
|
+
utils.decompress(compressedTexture('Rough_c', 2, 8)),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
expect(starts).toBe(1)
|
|
91
|
+
expect(fake.calls.render).toBe(3)
|
|
92
|
+
expect(fake.calls.sizes).toEqual([
|
|
93
|
+
[8, 4],
|
|
94
|
+
[16, 16],
|
|
95
|
+
[2, 8],
|
|
96
|
+
])
|
|
97
|
+
expect(drawn).toEqual([fake.domElement, fake.domElement, fake.domElement])
|
|
98
|
+
expect(fake.renderer.outputColorSpace).toBe(THREE.SRGBColorSpace)
|
|
99
|
+
|
|
100
|
+
const first = readable[0] as THREE.CanvasTexture
|
|
101
|
+
expect(first.isCanvasTexture).toBe(true)
|
|
102
|
+
expect(first.name).toBe('NormalGL_a')
|
|
103
|
+
expect(first.colorSpace).toBe(THREE.NoColorSpace)
|
|
104
|
+
expect(first.wrapS).toBe(THREE.RepeatWrapping)
|
|
105
|
+
expect(first.wrapT).toBe(THREE.MirroredRepeatWrapping)
|
|
106
|
+
expect(first.minFilter).toBe(THREE.NearestFilter)
|
|
107
|
+
expect(first.magFilter).toBe(THREE.NearestFilter)
|
|
108
|
+
|
|
109
|
+
await utils.dispose()
|
|
110
|
+
await utils.dispose()
|
|
111
|
+
expect(fake.calls.dispose).toBe(1)
|
|
112
|
+
expect(fake.calls.destroy).toBe(1)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('clamps the readback to maxTextureSize', async () => {
|
|
116
|
+
installCanvasDocument([])
|
|
117
|
+
const fake = fakeRenderer()
|
|
118
|
+
const utils = createExportTextureUtils(async () => fake.renderer)
|
|
119
|
+
|
|
120
|
+
const readable = await utils.decompress(compressedTexture('Color', 4096, 1024), 2048)
|
|
121
|
+
|
|
122
|
+
expect(fake.calls.sizes).toEqual([[2048, 1024]])
|
|
123
|
+
expect((readable.image as { width: number; height: number }).width).toBe(2048)
|
|
124
|
+
await utils.dispose()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('rejects when no GPU renderer can start instead of leaving the export pending', async () => {
|
|
128
|
+
installCanvasDocument([])
|
|
129
|
+
const utils = createExportTextureUtils(async () => {
|
|
130
|
+
throw new Error('no adapter')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await expect(utils.decompress(compressedTexture('Color'))).rejects.toThrow('no adapter')
|
|
134
|
+
await expect(utils.decompress(compressedTexture('Color'))).rejects.toThrow('no adapter')
|
|
135
|
+
await expect(utils.dispose()).resolves.toBeUndefined()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('disposing without decompressing never starts a renderer', async () => {
|
|
139
|
+
let starts = 0
|
|
140
|
+
const utils = createExportTextureUtils(async () => {
|
|
141
|
+
starts += 1
|
|
142
|
+
return fakeRenderer().renderer
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
await utils.dispose()
|
|
146
|
+
expect(starts).toBe(0)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('a decompress after dispose rejects instead of starting an unowned renderer', async () => {
|
|
150
|
+
installCanvasDocument([])
|
|
151
|
+
const fake = fakeRenderer()
|
|
152
|
+
let starts = 0
|
|
153
|
+
const utils = createExportTextureUtils(async () => {
|
|
154
|
+
starts += 1
|
|
155
|
+
return fake.renderer
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await utils.decompress(compressedTexture('Color'))
|
|
159
|
+
await utils.dispose()
|
|
160
|
+
await expect(utils.decompress(compressedTexture('Color'))).rejects.toThrow('abandoned')
|
|
161
|
+
await utils.dispose()
|
|
162
|
+
|
|
163
|
+
expect(starts).toBe(1)
|
|
164
|
+
expect(fake.calls.dispose).toBe(1)
|
|
165
|
+
expect(fake.calls.destroy).toBe(1)
|
|
166
|
+
})
|
|
167
|
+
})
|