@reekon-tools/boldr-cad 0.1.0 → 1.0.0
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/README.md +81 -9
- package/dist/app/edit.d.ts +12 -1
- package/dist/app/edit.js +96 -11
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +31 -4
- package/dist/app/state.js +31 -2
- package/dist/app/state.js.map +1 -1
- package/dist/data/memory.d.ts +5 -0
- package/dist/data/memory.js +2 -2
- package/dist/data/memory.js.map +1 -1
- package/dist/data/provider.d.ts +19 -7
- package/dist/data/sync.d.ts +7 -2
- package/dist/data/sync.js +115 -64
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +1 -2
- package/dist/editor/actions.js +391 -113
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/confirm.d.ts +23 -0
- package/dist/editor/confirm.js +35 -0
- package/dist/editor/confirm.js.map +1 -0
- package/dist/editor/elevation.d.ts +191 -90
- package/dist/editor/elevation.js +812 -616
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/face.d.ts +73 -0
- package/dist/editor/face.js +269 -0
- package/dist/editor/face.js.map +1 -0
- package/dist/editor/files.d.ts +6 -0
- package/dist/editor/files.js +50 -4
- package/dist/editor/files.js.map +1 -1
- package/dist/editor/gestures.d.ts +18 -3
- package/dist/editor/gestures.js +145 -30
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keypad.d.ts +19 -4
- package/dist/editor/keypad.js +39 -9
- package/dist/editor/keypad.js.map +1 -1
- package/dist/editor/keys.js +36 -4
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/laser.d.ts +20 -4
- package/dist/editor/laser.js +104 -21
- package/dist/editor/laser.js.map +1 -1
- package/dist/editor/linkActions.d.ts +49 -0
- package/dist/editor/linkActions.js +210 -0
- package/dist/editor/linkActions.js.map +1 -0
- package/dist/editor/linkFollow.d.ts +2 -0
- package/dist/editor/linkFollow.js +53 -0
- package/dist/editor/linkFollow.js.map +1 -0
- package/dist/editor/linkWrite.d.ts +13 -0
- package/dist/editor/linkWrite.js +84 -0
- package/dist/editor/linkWrite.js.map +1 -0
- package/dist/editor/links.d.ts +84 -0
- package/dist/editor/links.js +130 -0
- package/dist/editor/links.js.map +1 -0
- package/dist/editor/markup.d.ts +29 -3
- package/dist/editor/markup.js +95 -10
- package/dist/editor/markup.js.map +1 -1
- package/dist/editor/mode.d.ts +40 -2
- package/dist/editor/mode.js +71 -4
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/ports.d.ts +36 -6
- package/dist/editor/ports.js +10 -1
- package/dist/editor/ports.js.map +1 -1
- package/dist/editor/project.d.ts +38 -6
- package/dist/editor/project.js +167 -27
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/selection.d.ts +10 -0
- package/dist/editor/selection.js +62 -8
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/status.js +3 -0
- package/dist/editor/status.js.map +1 -1
- package/dist/editor/tooltips.d.ts +5 -0
- package/dist/editor/tooltips.js +48 -0
- package/dist/editor/tooltips.js.map +1 -0
- package/dist/editor/view3d.d.ts +17 -0
- package/dist/editor/view3d.js +135 -0
- package/dist/editor/view3d.js.map +1 -0
- package/dist/editor/view3dState.d.ts +39 -0
- package/dist/editor/view3dState.js +44 -0
- package/dist/editor/view3dState.js.map +1 -0
- package/dist/export/dxf.js +1 -1
- package/dist/export/dxf.js.map +1 -1
- package/dist/export/pdf.js +17 -12
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +23 -6
- package/dist/export/plan.js +127 -44
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/constraints.js +15 -4
- package/dist/model/constraints.js.map +1 -1
- package/dist/model/demo.js +3 -3
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +6 -9
- package/dist/model/elevation.js +5 -13
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/faces.d.ts +58 -0
- package/dist/model/faces.js +197 -0
- package/dist/model/faces.js.map +1 -0
- package/dist/model/facesolve.d.ts +72 -0
- package/dist/model/facesolve.js +369 -0
- package/dist/model/facesolve.js.map +1 -0
- package/dist/model/history.d.ts +3 -2
- package/dist/model/history.js +17 -11
- package/dist/model/history.js.map +1 -1
- package/dist/model/images.d.ts +18 -0
- package/dist/model/images.js +26 -0
- package/dist/model/images.js.map +1 -0
- package/dist/model/links.d.ts +46 -0
- package/dist/model/links.js +73 -0
- package/dist/model/links.js.map +1 -0
- package/dist/model/markup.d.ts +46 -0
- package/dist/model/markup.js +71 -0
- package/dist/model/markup.js.map +1 -0
- package/dist/model/measurement.d.ts +11 -0
- package/dist/model/measurement.js +24 -0
- package/dist/model/measurement.js.map +1 -0
- package/dist/model/openings.d.ts +4 -0
- package/dist/model/openings.js +4 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +72 -2
- package/dist/model/planGeom.js +184 -10
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +3 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/projectfile.js +2 -1
- package/dist/model/projectfile.js.map +1 -1
- package/dist/model/rooms.js +2 -2
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/settings.d.ts +6 -0
- package/dist/model/settings.js +2 -2
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +14 -0
- package/dist/model/sketch.js +143 -24
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/smooth.d.ts +6 -0
- package/dist/model/smooth.js +52 -0
- package/dist/model/smooth.js.map +1 -0
- package/dist/model/store.d.ts +12 -0
- package/dist/model/store.js +35 -3
- package/dist/model/store.js.map +1 -1
- package/dist/model/types.d.ts +173 -8
- package/dist/model/wallfaces.d.ts +32 -0
- package/dist/model/wallfaces.js +133 -20
- package/dist/model/wallfaces.js.map +1 -1
- package/dist/model/wallshape.d.ts +15 -0
- package/dist/model/wallshape.js +89 -0
- package/dist/model/wallshape.js.map +1 -0
- package/dist/react/BottomBar.d.ts +9 -5
- package/dist/react/BottomBar.js +213 -24
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Buttons.d.ts +13 -2
- package/dist/react/Buttons.js +37 -5
- package/dist/react/Buttons.js.map +1 -1
- package/dist/react/CadEditor.d.ts +46 -3
- package/dist/react/CadEditor.js +77 -20
- package/dist/react/CadEditor.js.map +1 -1
- package/dist/react/ConfirmSheet.d.ts +4 -0
- package/dist/react/ConfirmSheet.js +31 -0
- package/dist/react/ConfirmSheet.js.map +1 -0
- package/dist/react/ConflictSheet.d.ts +2 -1
- package/dist/react/ConflictSheet.js +16 -8
- package/dist/react/ConflictSheet.js.map +1 -1
- package/dist/react/Drawer.d.ts +8 -2
- package/dist/react/Drawer.js +167 -38
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.d.ts +13 -0
- package/dist/react/Duck.js +45 -0
- package/dist/react/Duck.js.map +1 -0
- package/dist/react/Empty3D.d.ts +2 -0
- package/dist/react/Empty3D.js +39 -0
- package/dist/react/Empty3D.js.map +1 -0
- package/dist/react/FitScroll.d.ts +6 -0
- package/dist/react/FitScroll.js +14 -0
- package/dist/react/FitScroll.js.map +1 -0
- package/dist/react/Icon.native.d.ts +6 -0
- package/dist/react/Icon.native.js +4 -0
- package/dist/react/Icon.native.js.map +1 -1
- package/dist/react/Icon.web.d.ts +6 -0
- package/dist/react/Icon.web.js +4 -0
- package/dist/react/Icon.web.js.map +1 -1
- package/dist/react/KeypadSheet.d.ts +9 -2
- package/dist/react/KeypadSheet.js +174 -42
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/LaserTray.js +8 -5
- package/dist/react/LaserTray.js.map +1 -1
- package/dist/react/MarkupPanel.d.ts +8 -0
- package/dist/react/MarkupPanel.js +67 -0
- package/dist/react/MarkupPanel.js.map +1 -0
- package/dist/react/PageThumb.native.d.ts +7 -2
- package/dist/react/PageThumb.native.js +19 -7
- package/dist/react/PageThumb.native.js.map +1 -1
- package/dist/react/PageThumb.web.d.ts +3 -2
- package/dist/react/PageThumb.web.js +4 -4
- package/dist/react/PageThumb.web.js.map +1 -1
- package/dist/react/PlanCanvas.native.d.ts +1 -1
- package/dist/react/PlanCanvas.native.js +134 -36
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/PlanCanvas.web.js +16 -5
- package/dist/react/PlanCanvas.web.js.map +1 -1
- package/dist/react/Popovers.d.ts +12 -6
- package/dist/react/Popovers.js +53 -26
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.d.ts +24 -0
- package/dist/react/Rail.js +324 -34
- package/dist/react/Rail.js.map +1 -1
- package/dist/react/ShapeMenu.d.ts +7 -0
- package/dist/react/ShapeMenu.js +58 -0
- package/dist/react/ShapeMenu.js.map +1 -0
- package/dist/react/Sheet.d.ts +14 -4
- package/dist/react/Sheet.js +54 -9
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/Slider.d.ts +18 -0
- package/dist/react/Slider.js +27 -0
- package/dist/react/Slider.js.map +1 -0
- package/dist/react/Toast.js +14 -6
- package/dist/react/Toast.js.map +1 -1
- package/dist/react/ToolTip.d.ts +7 -0
- package/dist/react/ToolTip.js +43 -0
- package/dist/react/ToolTip.js.map +1 -0
- package/dist/react/TopBar.d.ts +6 -3
- package/dist/react/TopBar.js +48 -17
- package/dist/react/TopBar.js.map +1 -1
- package/dist/react/Tween.d.ts +7 -0
- package/dist/react/Tween.js +26 -0
- package/dist/react/Tween.js.map +1 -0
- package/dist/react/Tween.native.d.ts +6 -0
- package/dist/react/Tween.native.js +43 -0
- package/dist/react/Tween.native.js.map +1 -0
- package/dist/react/View3D.native.d.ts +4 -0
- package/dist/react/View3D.native.js +103 -0
- package/dist/react/View3D.native.js.map +1 -0
- package/dist/react/View3D.web.d.ts +4 -0
- package/dist/react/View3D.web.js +151 -0
- package/dist/react/View3D.web.js.map +1 -0
- package/dist/react/View3DHost.d.ts +143 -0
- package/dist/react/View3DHost.js +377 -0
- package/dist/react/View3DHost.js.map +1 -0
- package/dist/react/corners.d.ts +7 -0
- package/dist/react/corners.js +51 -0
- package/dist/react/corners.js.map +1 -0
- package/dist/react/easing.d.ts +15 -0
- package/dist/react/easing.js +10 -0
- package/dist/react/easing.js.map +1 -0
- package/dist/react/host.d.ts +7 -2
- package/dist/react/host.js +8 -3
- package/dist/react/host.js.map +1 -1
- package/dist/react/hostKeypad.d.ts +56 -0
- package/dist/react/hostKeypad.js +5 -0
- package/dist/react/hostKeypad.js.map +1 -0
- package/dist/react/motion.d.ts +77 -0
- package/dist/react/motion.js +173 -0
- package/dist/react/motion.js.map +1 -0
- package/dist/react/ports.js +90 -8
- package/dist/react/ports.js.map +1 -1
- package/dist/react/saving.d.ts +15 -0
- package/dist/react/saving.js +76 -0
- package/dist/react/saving.js.map +1 -0
- package/dist/react/store.d.ts +38 -1
- package/dist/react/store.js +31 -1
- package/dist/react/store.js.map +1 -1
- package/dist/react/theme.d.ts +10 -0
- package/dist/react/theme.js +8 -0
- package/dist/react/theme.js.map +1 -1
- package/dist/react/topRow.d.ts +22 -0
- package/dist/react/topRow.js +61 -0
- package/dist/react/topRow.js.map +1 -0
- package/dist/react/touch.d.ts +6 -0
- package/dist/react/touch.js +9 -0
- package/dist/react/touch.js.map +1 -0
- package/dist/render/ctx.d.ts +8 -0
- package/dist/render/draw.d.ts +33 -1
- package/dist/render/draw.js +593 -215
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.d.ts +65 -43
- package/dist/render/elevation.js +414 -140
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +12 -0
- package/dist/render/figmaIcons.js +21 -0
- package/dist/render/figmaIcons.js.map +1 -0
- package/dist/render/hit.d.ts +22 -2
- package/dist/render/hit.js +168 -25
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +69 -10
- package/dist/render/icons.js.map +1 -1
- package/dist/render/linkTile.d.ts +15 -0
- package/dist/render/linkTile.js +369 -0
- package/dist/render/linkTile.js.map +1 -0
- package/dist/render/markup.d.ts +11 -0
- package/dist/render/markup.js +112 -0
- package/dist/render/markup.js.map +1 -0
- package/dist/render/navigate.d.ts +88 -0
- package/dist/render/navigate.js +100 -0
- package/dist/render/navigate.js.map +1 -0
- package/dist/render/snap.d.ts +20 -1
- package/dist/render/snap.js +118 -25
- package/dist/render/snap.js.map +1 -1
- package/dist/render/squircle.d.ts +8 -0
- package/dist/render/squircle.js +71 -0
- package/dist/render/squircle.js.map +1 -0
- package/dist/render/view.d.ts +115 -2
- package/dist/render/view.js +244 -19
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +30 -2
- package/dist/skia/SkiaDrawCtx.js +198 -43
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/skia/host.d.ts +18 -2
- package/dist/skia/host.js +46 -1
- package/dist/skia/host.js.map +1 -1
- package/dist/solver/equations.js +37 -2
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.d.ts +17 -0
- package/dist/solver/gauss-newton.js +144 -36
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/solver/linalg.d.ts +45 -2
- package/dist/solver/linalg.js +164 -10
- package/dist/solver/linalg.js.map +1 -1
- package/dist/solver/types.d.ts +5 -0
- package/dist/tools/annotate.d.ts +57 -1
- package/dist/tools/annotate.js +64 -43
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.d.ts +12 -0
- package/dist/tools/chamfer.js +114 -0
- package/dist/tools/chamfer.js.map +1 -0
- package/dist/tools/corner.d.ts +38 -0
- package/dist/tools/corner.js +99 -0
- package/dist/tools/corner.js.map +1 -0
- package/dist/tools/dim.d.ts +86 -9
- package/dist/tools/dim.js +581 -96
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/elevation.js +1 -1
- package/dist/tools/elevation.js.map +1 -1
- package/dist/tools/fillet.d.ts +9 -0
- package/dist/tools/fillet.js +86 -0
- package/dist/tools/fillet.js.map +1 -0
- package/dist/tools/freehand.d.ts +3 -2
- package/dist/tools/freehand.js +19 -12
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/index.js +14 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/line.d.ts +8 -3
- package/dist/tools/line.js +66 -30
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.d.ts +13 -0
- package/dist/tools/modify.js +105 -25
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/openings.js +22 -15
- package/dist/tools/openings.js.map +1 -1
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.js +42 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +77 -3
- package/dist/tools/select.js +560 -74
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +14 -0
- package/dist/tools/shapes.js +154 -75
- package/dist/tools/shapes.js.map +1 -1
- package/dist/units/format.d.ts +10 -2
- package/dist/units/format.js +18 -3
- package/dist/units/format.js.map +1 -1
- package/dist/view3d/build.d.ts +35 -0
- package/dist/view3d/build.js +630 -0
- package/dist/view3d/build.js.map +1 -0
- package/dist/view3d/camera.d.ts +60 -0
- package/dist/view3d/camera.js +84 -0
- package/dist/view3d/camera.js.map +1 -0
- package/dist/view3d/fatLines.d.ts +11 -0
- package/dist/view3d/fatLines.js +83 -0
- package/dist/view3d/fatLines.js.map +1 -0
- package/dist/view3d/lines.d.ts +17 -0
- package/dist/view3d/lines.js +271 -0
- package/dist/view3d/lines.js.map +1 -0
- package/dist/view3d/mesh.d.ts +51 -0
- package/dist/view3d/mesh.js +101 -0
- package/dist/view3d/mesh.js.map +1 -0
- package/dist/view3d/poly.d.ts +16 -0
- package/dist/view3d/poly.js +109 -0
- package/dist/view3d/poly.js.map +1 -0
- package/dist/view3d/viewer.d.ts +86 -0
- package/dist/view3d/viewer.js +350 -0
- package/dist/view3d/viewer.js.map +1 -0
- package/package.json +15 -2
- package/src/app/edit.ts +83 -12
- package/src/app/main.ts +11 -5
- package/src/app/state.ts +38 -5
- package/src/data/memory.ts +2 -2
- package/src/data/provider.ts +15 -7
- package/src/data/sync.ts +87 -50
- package/src/editor/actions.ts +292 -83
- package/src/editor/confirm.ts +58 -0
- package/src/editor/elevation.ts +698 -478
- package/src/editor/face.ts +227 -0
- package/src/editor/files.ts +40 -4
- package/src/editor/gestures.ts +147 -31
- package/src/editor/keypad.ts +43 -11
- package/src/editor/keys.ts +25 -5
- package/src/editor/laser.ts +98 -20
- package/src/editor/linkActions.ts +194 -0
- package/src/editor/linkFollow.ts +43 -0
- package/src/editor/linkWrite.ts +77 -0
- package/src/editor/links.ts +181 -0
- package/src/editor/markup.ts +92 -8
- package/src/editor/mode.ts +83 -7
- package/src/editor/ports.ts +37 -6
- package/src/editor/project.ts +155 -26
- package/src/editor/selection.ts +54 -8
- package/src/editor/status.ts +2 -0
- package/src/editor/tooltips.ts +47 -0
- package/src/editor/view3d.ts +119 -0
- package/src/editor/view3dState.ts +61 -0
- package/src/export/dxf.ts +1 -1
- package/src/export/pdf.ts +15 -7
- package/src/export/plan.ts +119 -35
- package/src/index.ts +5 -0
- package/src/input/keyboard.ts +2 -4
- package/src/model/constraints.ts +13 -4
- package/src/model/demo.ts +3 -3
- package/src/model/elevation.ts +6 -16
- package/src/model/faces.ts +153 -0
- package/src/model/facesolve.ts +322 -0
- package/src/model/history.ts +19 -11
- package/src/model/images.ts +35 -0
- package/src/model/links.ts +77 -0
- package/src/model/markup.ts +80 -0
- package/src/model/measurement.ts +26 -0
- package/src/model/openings.ts +6 -0
- package/src/model/planGeom.ts +174 -8
- package/src/model/project.ts +3 -2
- package/src/model/projectfile.ts +2 -1
- package/src/model/rooms.ts +1 -1
- package/src/model/settings.ts +8 -2
- package/src/model/sketch.ts +101 -14
- package/src/model/smooth.ts +51 -0
- package/src/model/store.ts +35 -4
- package/src/model/types.ts +170 -8
- package/src/model/wallfaces.ts +126 -17
- package/src/model/wallshape.ts +97 -0
- package/src/react/BottomBar.tsx +263 -46
- package/src/react/Buttons.tsx +50 -9
- package/src/react/CadEditor.tsx +133 -30
- package/src/react/ConfirmSheet.tsx +38 -0
- package/src/react/ConflictSheet.tsx +19 -12
- package/src/react/Drawer.tsx +213 -110
- package/src/react/Duck.tsx +50 -0
- package/src/react/Empty3D.tsx +32 -0
- package/src/react/FitScroll.tsx +19 -0
- package/src/react/Icon.native.tsx +5 -0
- package/src/react/Icon.web.tsx +5 -0
- package/src/react/KeypadSheet.tsx +253 -60
- package/src/react/LaserTray.tsx +12 -8
- package/src/react/MarkupPanel.tsx +150 -0
- package/src/react/PageThumb.native.tsx +18 -7
- package/src/react/PageThumb.web.tsx +4 -4
- package/src/react/PlanCanvas.native.tsx +151 -34
- package/src/react/PlanCanvas.web.tsx +15 -5
- package/src/react/Popovers.tsx +116 -51
- package/src/react/Rail.tsx +360 -27
- package/src/react/ShapeMenu.tsx +66 -0
- package/src/react/Sheet.tsx +56 -11
- package/src/react/Slider.tsx +48 -0
- package/src/react/Toast.tsx +16 -9
- package/src/react/ToolTip.tsx +49 -0
- package/src/react/TopBar.tsx +97 -39
- package/src/react/Tween.native.tsx +36 -0
- package/src/react/Tween.tsx +24 -0
- package/src/react/View3D.native.tsx +131 -0
- package/src/react/View3D.web.tsx +131 -0
- package/src/react/View3DHost.ts +367 -0
- package/src/react/corners.ts +52 -0
- package/src/react/easing.ts +14 -0
- package/src/react/host.ts +8 -3
- package/src/react/hostKeypad.ts +66 -0
- package/src/react/motion.tsx +239 -0
- package/src/react/ports.ts +78 -9
- package/src/react/saving.ts +80 -0
- package/src/react/store.ts +35 -3
- package/src/react/theme.ts +8 -0
- package/src/react/topRow.ts +71 -0
- package/src/react/touch.ts +10 -0
- package/src/render/ctx.ts +8 -0
- package/src/render/draw.ts +437 -175
- package/src/render/elevation.ts +349 -143
- package/src/render/figmaIcons.ts +22 -0
- package/src/render/hit.ts +140 -21
- package/src/render/icons.ts +69 -10
- package/src/render/linkTile.ts +297 -0
- package/src/render/markup.ts +91 -0
- package/src/render/navigate.ts +114 -0
- package/src/render/snap.ts +105 -22
- package/src/render/squircle.ts +73 -0
- package/src/render/view.ts +255 -19
- package/src/skia/SkiaDrawCtx.ts +163 -41
- package/src/skia/host.ts +46 -1
- package/src/solver/equations.ts +37 -2
- package/src/solver/gauss-newton.ts +125 -28
- package/src/solver/linalg.ts +141 -5
- package/src/solver/types.ts +5 -0
- package/src/styles.css +91 -42
- package/src/tools/annotate.ts +58 -29
- package/src/tools/chamfer.ts +103 -0
- package/src/tools/corner.ts +99 -0
- package/src/tools/dim.ts +524 -92
- package/src/tools/elevation.ts +1 -1
- package/src/tools/fillet.ts +82 -0
- package/src/tools/freehand.ts +20 -16
- package/src/tools/index.ts +17 -4
- package/src/tools/line.ts +59 -23
- package/src/tools/modify.ts +86 -24
- package/src/tools/openings.ts +8 -13
- package/src/tools/registry.ts +27 -2
- package/src/tools/select.ts +424 -75
- package/src/tools/shapes.ts +119 -58
- package/src/ui/ctx.ts +2 -1
- package/src/ui/domPorts.ts +23 -4
- package/src/ui/drawer.ts +6 -26
- package/src/ui/elevation.ts +10 -101
- package/src/ui/keypad.ts +8 -14
- package/src/ui/laser.ts +2 -2
- package/src/ui/menu.ts +63 -17
- package/src/ui/noteinput.ts +3 -3
- package/src/ui/opacity.ts +108 -0
- package/src/ui/palette.ts +67 -12
- package/src/ui/rail.ts +21 -7
- package/src/ui/topbar.ts +4 -14
- package/src/units/format.ts +18 -3
- package/src/view3d/build.ts +607 -0
- package/src/view3d/camera.ts +113 -0
- package/src/view3d/fatLines.ts +83 -0
- package/src/view3d/lines.ts +258 -0
- package/src/view3d/mesh.ts +111 -0
- package/src/view3d/poly.ts +104 -0
- package/src/view3d/viewer.ts +348 -0
- package/dist/react/ElevCanvas.native.d.ts +0 -3
- package/dist/react/ElevCanvas.native.js +0 -58
- package/dist/react/ElevCanvas.native.js.map +0 -1
- package/dist/react/ElevCanvas.web.d.ts +0 -4
- package/dist/react/ElevCanvas.web.js +0 -44
- package/dist/react/ElevCanvas.web.js.map +0 -1
- package/dist/react/ElevationSheet.d.ts +0 -2
- package/dist/react/ElevationSheet.js +0 -38
- package/dist/react/ElevationSheet.js.map +0 -1
- package/dist/render/elevationView.d.ts +0 -3
- package/dist/render/elevationView.js +0 -100
- package/dist/render/elevationView.js.map +0 -1
- package/src/react/ElevCanvas.native.tsx +0 -68
- package/src/react/ElevCanvas.web.tsx +0 -42
- package/src/react/ElevationSheet.tsx +0 -88
- package/src/render/elevationView.ts +0 -73
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { solver, type SolveResult } from '../solver';
|
|
2
|
+
import { elevFrame, elevOpenings, roomSideOf, toU, type ElevFrame, type ElevOpening } from './elevation';
|
|
3
|
+
import { applySlide, openingSpan, slideGroup } from './openings';
|
|
4
|
+
import { onSessionReset } from './session';
|
|
5
|
+
import { CEILING, cornerAnchor, emptyFace, endEdge, faceKey, faceKeyParts, faceOf, FLOOR, openingAnchor, openingEdge, openingOfPoint } from './faces';
|
|
6
|
+
import { uid } from './ids';
|
|
7
|
+
import { gc, restorePos, snapshotPos } from './sketch';
|
|
8
|
+
import type { Constraint, Pt, Sketch, WallFace } from './types';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Keeping wall faces in step with the plan. A face holds the plan's corners, floor, ceiling and the doors' and
|
|
12
|
+
windows' outlines as anchors (named points and edges); after any change they're moved to where the plan has them
|
|
13
|
+
and the face is solved again, so what's drawn on a wall follows its corners and openings. A door or window that's
|
|
14
|
+
gone lets go of what hung on it (its corners become free points there).
|
|
15
|
+
|
|
16
|
+
The wall's corners, floor and ceiling are fixed. A door or window is fixed only where the plan locks it (along the
|
|
17
|
+
wall by a dimension, up and down by its sill — a door sits on the floor); otherwise its corners are free points
|
|
18
|
+
held to its size and shape, like a cabinet's, so a constraint or a join on the wall can move it — and where the
|
|
19
|
+
face moves it, the plan follows.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Where each face's door/window corners were last put (face key → point → [x, y]): which side moved one since. */
|
|
23
|
+
const placed = new Map<string, Record<string, [number, number]>>();
|
|
24
|
+
onSessionReset(() => placed.clear());
|
|
25
|
+
|
|
26
|
+
/** Where each face's anchors were when it was last brought up to date (face key → their places): unchanged, it needn't be again. */
|
|
27
|
+
const synced = new Map<string, string>();
|
|
28
|
+
onSessionReset(() => synced.clear());
|
|
29
|
+
const anchorsAt = (sk: Sketch, f: ElevFrame): string => JSON.stringify(anchorSpec(sk, f).points) + '|' + f.H;
|
|
30
|
+
/** Forget which faces are up to date (they were put back as they were): the next sync does them all. */
|
|
31
|
+
export const forgetSynced = (): void => synced.clear();
|
|
32
|
+
|
|
33
|
+
/** A copy of where the faces' door/window corners were last put, and putting it back (an edit tried and taken back). */
|
|
34
|
+
export const placedMarks = (): string => JSON.stringify([...placed]);
|
|
35
|
+
export function setPlacedMarks(m: string): void {
|
|
36
|
+
placed.clear();
|
|
37
|
+
for (const [k, v] of JSON.parse(m) as [string, Record<string, [number, number]>][]) placed.set(k, v);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** How a door or window on a face is held: fixed outright, or free along the wall and/or up and down. */
|
|
41
|
+
function holds(sk: Sketch, o: ElevOpening): { along: boolean; up: boolean } {
|
|
42
|
+
const op = sk.openings[o.id];
|
|
43
|
+
return { along: !!slideGroup(sk, o.id), up: o.type === 'window' && !op?.sillLocked };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The frame of wall `lid` seen from `side` (whichever side the room is on). Null for anything but a straight wall. */
|
|
47
|
+
export function faceFrame(sk: Sketch, lid: string, side: 1 | -1): ElevFrame | null {
|
|
48
|
+
if (!sk.lines[lid]) return null;
|
|
49
|
+
return elevFrame(sk, lid, side !== (roomSideOf(sk, lid) || 1));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Where the plan puts a face's anchors (x across the wall as seen, y up), and the edges between them. */
|
|
53
|
+
export function anchorSpec(sk: Sketch, f: ElevFrame): { points: Record<string, [number, number]>; lines: Record<string, [string, string]> } {
|
|
54
|
+
const other = f.leftEnd === 'a' ? 'b' : 'a', at: Record<'a' | 'b', number> = { [f.leftEnd]: f.face[0], [other]: f.face[1] } as Record<'a' | 'b', number>;
|
|
55
|
+
const points: Record<string, [number, number]> = {}, lines: Record<string, [string, string]> = {};
|
|
56
|
+
for (const end of ['a', 'b'] as const) {
|
|
57
|
+
points[cornerAnchor(end)] = [at[end], 0];
|
|
58
|
+
points[cornerAnchor(end, true)] = [at[end], f.H];
|
|
59
|
+
lines[endEdge(end)] = [cornerAnchor(end), cornerAnchor(end, true)];
|
|
60
|
+
}
|
|
61
|
+
lines[FLOOR] = [cornerAnchor(f.leftEnd), cornerAnchor(other)];
|
|
62
|
+
lines[CEILING] = [cornerAnchor(f.leftEnd, true), cornerAnchor(other, true)];
|
|
63
|
+
for (const o of elevOpenings(sk, f)) {
|
|
64
|
+
const p = (e: 's0' | 's1', top: boolean) => openingAnchor(o.id, e, top);
|
|
65
|
+
points[p(o.e0, false)] = [o.u0, o.sill]; points[p(o.e0, true)] = [o.u0, o.head];
|
|
66
|
+
points[p(o.e1, false)] = [o.u1, o.sill]; points[p(o.e1, true)] = [o.u1, o.head];
|
|
67
|
+
lines[openingEdge(o.id, 'head')] = [p(o.e0, true), p(o.e1, true)];
|
|
68
|
+
lines[openingEdge(o.id, 's0')] = [p('s0', false), p('s0', true)];
|
|
69
|
+
lines[openingEdge(o.id, 's1')] = [p('s1', false), p('s1', true)];
|
|
70
|
+
if (o.type === 'window') lines[openingEdge(o.id, 'sill')] = [p(o.e0, false), p(o.e1, false)]; // a door's sill is the floor
|
|
71
|
+
}
|
|
72
|
+
return { points, lines };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Bring a face up to date from earlier versions: lines drawn on elevations (Sketch.elev) and faces that kept points
|
|
77
|
+
* along the wall from its start (no curves yet) become this format, across the wall as seen.
|
|
78
|
+
*/
|
|
79
|
+
function upgrade(sk: Sketch, key: string, f: ElevFrame): void {
|
|
80
|
+
type Old = { points: Record<string, { x: number; y: number }>; lines: Record<string, { a: string; b: string; guide?: boolean; cab?: string }>; cons: Record<string, Constraint>; cabs?: Record<string, { bl: string; br: string; tr: string; tl: string; w?: string; h?: string; depth: number; label?: string }> };
|
|
81
|
+
const cur = sk.faces?.[key] as unknown as Old & { circles?: unknown } | undefined;
|
|
82
|
+
if (cur && !cur.circles) {
|
|
83
|
+
const face = emptyFace();
|
|
84
|
+
for (const [id, p] of Object.entries(cur.points)) face.points[id] = { x: toU(f, p.x), y: p.y };
|
|
85
|
+
for (const [id, l] of Object.entries(cur.lines)) face.lines[id] = { a: l.a, b: l.b, kind: 'line', constr: !!l.guide, thick: 0, ...(l.cab ? { cab: l.cab } : {}) };
|
|
86
|
+
face.cons = cur.cons;
|
|
87
|
+
for (const [id, cb] of Object.entries(cur.cabs ?? {})) {
|
|
88
|
+
// corners as seen: bottom-left was the one at the smaller s
|
|
89
|
+
const sw = f.mirror, { w, h, depth, label } = cb;
|
|
90
|
+
face.cabs[id] = { bl: sw ? cb.br : cb.bl, br: sw ? cb.bl : cb.br, tr: sw ? cb.tl : cb.tr, tl: sw ? cb.tr : cb.tl, depth, ...(w ? { w } : {}), ...(h ? { h } : {}), ...(label ? { label } : {}) };
|
|
91
|
+
}
|
|
92
|
+
// directions along the wall flip in a mirrored view
|
|
93
|
+
if (f.mirror) for (const c of Object.values(face.cons)) if (c.type === 'dist' && c.kind === 'h' && c.sign) c.sign = -c.sign as 1 | -1;
|
|
94
|
+
sk.faces![key] = face;
|
|
95
|
+
}
|
|
96
|
+
const old = sk.elev?.[key];
|
|
97
|
+
if (old?.length) {
|
|
98
|
+
const face = faceOf(sk, key, true);
|
|
99
|
+
for (const ln of old) {
|
|
100
|
+
const a = uid('p'), b = uid('p');
|
|
101
|
+
face.points[a] = { x: toU(f, ln.a[0]), y: ln.a[1] };
|
|
102
|
+
face.points[b] = { x: toU(f, ln.b[0]), y: ln.b[1] };
|
|
103
|
+
face.lines[ln.id || uid('l')] = { a, b, kind: 'line', constr: !!ln.guide, thick: 0 };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (sk.elev) { delete sk.elev[key]; if (!Object.keys(sk.elev).length) delete sk.elev; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Move a face's anchors to where the plan has them; anchors the plan no longer has let go (free points), and their edges go. */
|
|
110
|
+
export function placeAnchors(sk: Sketch, face: WallFace, f: ElevFrame): boolean {
|
|
111
|
+
let gone = false;
|
|
112
|
+
const spec = anchorSpec(sk, f), ops = elevOpenings(sk, f);
|
|
113
|
+
// a door's or window's corners are fixed only when the plan holds it both ways
|
|
114
|
+
const free = new Set<string>();
|
|
115
|
+
for (const o of ops) { const h = holds(sk, o); if (h.along || h.up) for (const e of ['s0', 's1'] as const) for (const t of [false, true]) free.add(openingAnchor(o.id, e, t)); }
|
|
116
|
+
for (const [id, [x, y]] of Object.entries(spec.points)) {
|
|
117
|
+
const p = face.points[id], fixed = !free.has(id);
|
|
118
|
+
if (p && p.anchor) { p.x = x; p.y = y; if (fixed) p.fixed = true; else delete p.fixed; }
|
|
119
|
+
else if (p) Object.assign(p, { x, y, anchor: true }, fixed ? { fixed: true } : {}); // a copy that came along with a split wall
|
|
120
|
+
else face.points[id] = { x, y, anchor: true, ...(fixed ? { fixed: true } : {}) };
|
|
121
|
+
if (!fixed) delete face.points[id].fixed;
|
|
122
|
+
}
|
|
123
|
+
// the shape of each free one (and where the plan does lock it), as the plan's own constraints on the face
|
|
124
|
+
const keep = new Set<string>();
|
|
125
|
+
const put = (id: string, c: Constraint) => { keep.add(id); face.cons[id] = { ...c, driving: c.type === 'dist' ? true : undefined, auto: true, elev: true, anchor: true }; if (c.type !== 'dist') delete face.cons[id].driving; };
|
|
126
|
+
for (const o of ops) {
|
|
127
|
+
const h = holds(sk, o);
|
|
128
|
+
if (!h.along && !h.up) continue;
|
|
129
|
+
const p = (e: 's0' | 's1', top: boolean) => openingAnchor(o.id, e, top), k = (n: string) => '@k:' + o.id + ':' + n;
|
|
130
|
+
const corner = cornerAnchor(f.leftEnd);
|
|
131
|
+
put(k('l'), { type: 'v', a: p(o.e0, false), b: p(o.e0, true) });
|
|
132
|
+
put(k('r'), { type: 'v', a: p(o.e1, false), b: p(o.e1, true) });
|
|
133
|
+
put(k('b'), { type: 'h', a: p(o.e0, false), b: p(o.e1, false) });
|
|
134
|
+
put(k('t'), { type: 'h', a: p(o.e0, true), b: p(o.e1, true) });
|
|
135
|
+
put(k('w'), { type: 'dist', kind: 'h', a: p(o.e0, false), b: p(o.e1, false), value: o.u1 - o.u0 });
|
|
136
|
+
put(k('h'), { type: 'dist', kind: 'v', a: p(o.e0, false), b: p(o.e0, true), value: o.head - o.sill });
|
|
137
|
+
if (!h.along) put(k('at'), { type: 'dist', kind: 'h', a: corner, b: p(o.e0, false), value: o.u0 - f.face[0] });
|
|
138
|
+
if (!h.up) put(k('z'), { type: 'dist', kind: 'v', a: corner, b: p(o.e0, false), value: o.sill });
|
|
139
|
+
}
|
|
140
|
+
for (const id in face.cons) if (face.cons[id].anchor && !keep.has(id)) delete face.cons[id];
|
|
141
|
+
for (const [id, [a, b]] of Object.entries(spec.lines)) {
|
|
142
|
+
const l = face.lines[id];
|
|
143
|
+
if (l && l.anchor && l.a === a && l.b === b) continue;
|
|
144
|
+
face.lines[id] = { a, b, kind: 'line', constr: false, thick: 0, anchor: true };
|
|
145
|
+
}
|
|
146
|
+
for (const id in face.lines) if (face.lines[id].anchor && !spec.lines[id]) delete face.lines[id];
|
|
147
|
+
for (const id in face.points) {
|
|
148
|
+
const p: Pt = face.points[id];
|
|
149
|
+
if (p.anchor && !spec.points[id]) { delete p.anchor; delete p.fixed; gone = true; }
|
|
150
|
+
}
|
|
151
|
+
return gone;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Update one face from the plan and solve it. Positions stay as they were where it can't be solved. Null: not a wall now. */
|
|
155
|
+
export function syncFace(sk: Sketch, key: string): SolveResult | null {
|
|
156
|
+
const { lid, side } = faceKeyParts(key), f = faceFrame(sk, lid, side);
|
|
157
|
+
if (!f) return null;
|
|
158
|
+
upgrade(sk, key, f);
|
|
159
|
+
const face = faceOf(sk, key);
|
|
160
|
+
if (!face) return null;
|
|
161
|
+
// the face moved a door or window since it was last put there (a constraint, a join, a drag on the wall) and the
|
|
162
|
+
// plan didn't: the plan follows it first
|
|
163
|
+
const last = placed.get(key), moved = new Set<string>();
|
|
164
|
+
if (last) {
|
|
165
|
+
const spec = anchorSpec(sk, f).points;
|
|
166
|
+
for (const o of elevOpenings(sk, f)) {
|
|
167
|
+
const id = openingAnchor(o.id, o.e0, false), q = face.points[id], was = last[id], now = spec[id];
|
|
168
|
+
if (!q || !was || !now) continue;
|
|
169
|
+
const faceMoved = Math.abs(q.x - was[0]) > 1e-6 || Math.abs(q.y - was[1]) > 1e-6;
|
|
170
|
+
const planMoved = Math.abs(now[0] - was[0]) > 1e-6 || Math.abs(now[1] - was[1]) > 1e-6;
|
|
171
|
+
if (faceMoved && !planMoved) { followFace(sk, f, face, o); moved.add(o.id); }
|
|
172
|
+
else if (planMoved) moved.add(o.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
stayInPlace(face, f);
|
|
176
|
+
// a door or window gone: its corners let go — and go, unless something still uses them
|
|
177
|
+
if (placeAnchors(sk, face, f)) gc(face);
|
|
178
|
+
let r: SolveResult = { ok: true, res: 0, bad: [] };
|
|
179
|
+
if (Object.values(face.cons).some(c => !c.anchor)) {
|
|
180
|
+
// what just moved stays put; what's tied to it follows
|
|
181
|
+
const heavy = [...moved].flatMap(op => (['s0', 's1'] as const).flatMap(e => [openingAnchor(op, e), openingAnchor(op, e, true)]));
|
|
182
|
+
// (and nothing else drifts as they're put in line: see solveHolding)
|
|
183
|
+
const snap = snapshotPos(face);
|
|
184
|
+
r = solver.solveHolding(face, heavy);
|
|
185
|
+
if (!r.ok) restorePos(face, snap);
|
|
186
|
+
else for (const o of elevOpenings(sk, f)) followFace(sk, f, face, o);
|
|
187
|
+
}
|
|
188
|
+
const at: Record<string, [number, number]> = {};
|
|
189
|
+
for (const [id, p] of Object.entries(face.points)) if (p.anchor && openingOfPoint(id)) at[id] = [p.x, p.y];
|
|
190
|
+
placed.set(key, at);
|
|
191
|
+
synced.set(key, anchorsAt(sk, f));
|
|
192
|
+
return r;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* One end of the wall moved (a corner dragged, a length entered): what's drawn on the face stays where it is, so it
|
|
197
|
+
* keeps its distance from the end that moved less — the whole wall moved, it goes along. (Across a face, x counts
|
|
198
|
+
* from one of the wall's ends, so without this it would ride along with that end.) What's tied to a corner by a
|
|
199
|
+
* dimension follows it again when the face is solved.
|
|
200
|
+
*/
|
|
201
|
+
function stayInPlace(face: WallFace, f: ElevFrame): void {
|
|
202
|
+
const at = (u: number): [number, number] => { const s = toU(f, u); return [f.A.x + f.ux * s, f.A.y + f.uy * s]; };
|
|
203
|
+
const now: NonNullable<WallFace['wall']> = { u: [f.face[0], f.face[1]], ends: [at(f.face[0]), at(f.face[1])] }, was = face.wall;
|
|
204
|
+
face.wall = now;
|
|
205
|
+
if (!was) return;
|
|
206
|
+
const moved = (i: 0 | 1) => Math.hypot(now.ends[i][0] - was.ends[i][0], now.ends[i][1] - was.ends[i][1]);
|
|
207
|
+
const i = moved(0) <= moved(1) ? 0 : 1, du = now.u[i] - was.u[i];
|
|
208
|
+
if (Math.abs(du) < 1e-9) return;
|
|
209
|
+
for (const p of Object.values(face.points)) if (!p.anchor) p.x += du;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Where the face has door or window `o` (its bottom-left corner as seen), the plan puts it: slid along the wall, its sill. */
|
|
213
|
+
function followFace(sk: Sketch, f: ElevFrame, face: WallFace, o: ElevOpening): void {
|
|
214
|
+
const q = face.points[openingAnchor(o.id, o.e0, false)], op = sk.openings[o.id];
|
|
215
|
+
if (!q || !op) return;
|
|
216
|
+
const h = holds(sk, o), du = q.x - o.u0, g = h.along ? slideGroup(sk, o.id) : null;
|
|
217
|
+
if (g && Math.abs(du) > 1e-6) applySlide(sk, g, f.mirror ? -du : du);
|
|
218
|
+
if (h.up && Math.abs(q.y - o.sill) > 1e-6) op.sill = Math.max(0, q.y);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Update every wall face from the plan (after any edit). Returns the face constraints that can't be met. `moved`: only
|
|
223
|
+
* the faces whose wall has moved since they were last brought up to date (while a wall is dragged, the rest can't have
|
|
224
|
+
* changed).
|
|
225
|
+
*/
|
|
226
|
+
export function syncFaces(sk: Sketch, moved = false): Set<string> {
|
|
227
|
+
const keys = new Set([...Object.keys(sk.faces ?? {}), ...Object.keys(sk.elev ?? {})]), bad = new Set<string>();
|
|
228
|
+
for (const key of keys) {
|
|
229
|
+
if (moved && synced.has(key)) {
|
|
230
|
+
const { lid, side } = faceKeyParts(key), f = faceFrame(sk, lid, side);
|
|
231
|
+
if (f && synced.get(key) === anchorsAt(sk, f)) continue;
|
|
232
|
+
}
|
|
233
|
+
const r = syncFace(sk, key);
|
|
234
|
+
if (r && !r.ok) for (const id of r.bad) bad.add(id);
|
|
235
|
+
}
|
|
236
|
+
return bad;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The face of wall `lid` seen from `side`, made if there's none, up to date with the plan. Null for anything but a straight wall. */
|
|
240
|
+
export function openFace(sk: Sketch, lid: string, side: 1 | -1): WallFace | null {
|
|
241
|
+
const key = faceKey(lid, side);
|
|
242
|
+
if (!faceFrame(sk, lid, side)) return null;
|
|
243
|
+
faceOf(sk, key, true);
|
|
244
|
+
syncFace(sk, key);
|
|
245
|
+
return faceOf(sk, key);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** A cabinet as the plan shows it: its footprint against its wall (read-only, from the wall's view). */
|
|
249
|
+
export interface CabFootprint { key: string; id: string; lid: string; pts: { x: number; y: number }[]; upper: boolean }
|
|
250
|
+
|
|
251
|
+
/** Cabinets hung this far off the floor or more are wall (upper) cabinets: dashed in the plan. */
|
|
252
|
+
export const UPPER_Z = 12;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Every cabinet's footprint in the plan: along its wall where it sits on the face, out from that face of the wall by
|
|
256
|
+
* its depth. Worked out from each wall's view each time, so it follows the wall and the cabinet; never stored.
|
|
257
|
+
*/
|
|
258
|
+
export function cabinetFootprints(sk: Sketch): CabFootprint[] {
|
|
259
|
+
const out: CabFootprint[] = [];
|
|
260
|
+
for (const [key, face] of Object.entries(sk.faces ?? {})) {
|
|
261
|
+
if (!face.cabs || !Object.keys(face.cabs).length) continue;
|
|
262
|
+
const { lid, side } = faceKeyParts(key), f = faceFrame(sk, lid, side);
|
|
263
|
+
if (!f) continue;
|
|
264
|
+
const nx = -f.uy * f.side, ny = f.ux * f.side;
|
|
265
|
+
const P = (s: number, k: number) => ({ x: f.A.x + f.ux * s + nx * k, y: f.A.y + f.uy * s + ny * k });
|
|
266
|
+
for (const [id, cb] of Object.entries(face.cabs)) {
|
|
267
|
+
const ps = [cb.bl, cb.br, cb.tr, cb.tl].map(p => face.points[p]);
|
|
268
|
+
if (ps.some(p => !p)) continue;
|
|
269
|
+
const s0 = toU(f, Math.min(...ps.map(p => p.x))), s1 = toU(f, Math.max(...ps.map(p => p.x))), z0 = Math.min(...ps.map(p => p.y));
|
|
270
|
+
const k0 = f.t / 2, k1 = f.t / 2 + cb.depth;
|
|
271
|
+
out.push({ key, id, lid, pts: [P(s0, k0), P(s1, k0), P(s1, k1), P(s0, k1)], upper: z0 >= UPPER_Z });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A wall face's edges: across from corner to corner, up from the floor to the ceiling. */
|
|
278
|
+
export interface FaceBounds { x0: number; x1: number; y0: number; y1: number }
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* How far each thing on the plan's walls reaches past them, inches — only what does (an empty record: it all fits).
|
|
282
|
+
* Kept per thing, not as one total, so a change can be held to "nothing goes further out than it was": as a total,
|
|
283
|
+
* one thing coming back in hid another being pushed out (a window sliding back inside the wall on one side of it
|
|
284
|
+
* while a cabinet tied to it went off the other end).
|
|
285
|
+
*/
|
|
286
|
+
export type Overflow = Record<string, number>;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* What's on a face past its edges: each point, each circle's reach — and the doors' and windows' corners too (a
|
|
290
|
+
* cabinet tied to a window could otherwise push the window through the ceiling, and dragging it back down then drove
|
|
291
|
+
* the cabinet through the floor). Keys start with `prefix`.
|
|
292
|
+
*/
|
|
293
|
+
export function faceOverflow(face: Sketch, b: FaceBounds, prefix = '', out: Overflow = {}): Overflow {
|
|
294
|
+
const past = (x: number, y: number, r = 0) => Math.max(0, b.x0 - (x - r)) + Math.max(0, x + r - b.x1) + Math.max(0, b.y0 - (y - r)) + Math.max(0, y + r - b.y1);
|
|
295
|
+
const put = (k: string, n: number) => { if (n > 1e-9) out[prefix + k] = n; };
|
|
296
|
+
for (const [id, p] of Object.entries(face.points)) if (!p.anchor || openingOfPoint(id)) put(id, past(p.x, p.y));
|
|
297
|
+
for (const [id, c] of Object.entries(face.circles)) { const o = face.points[c.c]; if (o) put('c:' + id, past(o.x, o.y, c.r)); }
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* What runs past the plan's walls: each door and window past its wall's ends, and what's drawn on each wall face
|
|
303
|
+
* (cabinets and all) past that face's corners, floor and ceiling.
|
|
304
|
+
*/
|
|
305
|
+
export function planOverflow(sk: Sketch): Overflow {
|
|
306
|
+
const out: Overflow = {};
|
|
307
|
+
for (const [id, o] of Object.entries(sk.openings)) {
|
|
308
|
+
const g = openingSpan(sk, o), n = g ? Math.max(0, o.w / 2 - o.at) + Math.max(0, o.at + o.w / 2 - g.L) : 0;
|
|
309
|
+
if (n > 1e-9) out['op:' + id] = n;
|
|
310
|
+
}
|
|
311
|
+
for (const [key, face] of Object.entries(sk.faces ?? {})) {
|
|
312
|
+
const { lid, side } = faceKeyParts(key), f = faceFrame(sk, lid, side);
|
|
313
|
+
if (f) faceOverflow(face, { x0: f.face[0], x1: f.face[1], y0: 0, y1: f.H }, key + '|', out);
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Whether anything is further past its wall in `now` than it was in `was` (what was already past it may stay, or come back in). */
|
|
319
|
+
export function pushedOut(was: Overflow, now: Overflow): boolean {
|
|
320
|
+
for (const k in now) if (now[k] > (was[k] ?? 0) + 1e-6) return true;
|
|
321
|
+
return false;
|
|
322
|
+
}
|
package/src/model/history.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { page,
|
|
1
|
+
import { page, store } from './store';
|
|
2
2
|
import type { Underlay } from './types';
|
|
3
3
|
import { onSessionReset } from './session';
|
|
4
4
|
|
|
5
5
|
/* Undo/redo as JSON snapshots per page, as in the prototype.
|
|
6
6
|
(An operation log is a later roadmap item for sync.)
|
|
7
|
-
Each snapshot also records the
|
|
8
|
-
|
|
7
|
+
Each snapshot also records the page's images — which there are, and where — so adding, replacing, moving,
|
|
8
|
+
scaling, locking and removing them undo like everything else. Their pictures aren't copied: a snapshot keeps the
|
|
9
|
+
same reference (a data URL or a stored image's address) the page has. */
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
interface Snap { sk: string; img: ImgXf }
|
|
11
|
+
interface Snap { sk: string; imgs: Underlay[] }
|
|
12
12
|
|
|
13
13
|
const hist: Record<string, { u: Snap[]; r: Snap[] }> = {};
|
|
14
14
|
onSessionReset(() => { for (const k in hist) delete hist[k]; });
|
|
@@ -16,17 +16,25 @@ onSessionReset(() => { for (const k in hist) delete hist[k]; });
|
|
|
16
16
|
export const HS = () => hist[store.curPageId] || (hist[store.curPageId] = { u: [], r: [] });
|
|
17
17
|
|
|
18
18
|
function take(): Snap {
|
|
19
|
-
|
|
20
|
-
let img: ImgXf = null;
|
|
21
|
-
if (u) { const { src: _src, ...xf } = u; img = xf; }
|
|
22
|
-
return { sk: JSON.stringify(SK()), img };
|
|
19
|
+
return { sk: JSON.stringify(page().sk), imgs: page().underlays.map(u => ({ ...u })) }; // the whole page: its plan and its wall faces
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
function restore(s: Snap): void {
|
|
26
23
|
const pg = page();
|
|
27
24
|
pg.sk = JSON.parse(s.sk);
|
|
28
|
-
//
|
|
29
|
-
|
|
25
|
+
// an image uploaded since keeps its stored address (not the local copy it had when this was taken)
|
|
26
|
+
const now = new Map(pg.underlays.map(u => [u.id, u.src]));
|
|
27
|
+
pg.underlays = s.imgs.map(u => ({ ...u, src: stored(u.src, now.get(u.id)) }));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The newer of two references to the same picture: a stored address beats the local data it was uploaded from. */
|
|
31
|
+
const stored = (then: string, now: string | undefined): string => (now && then.startsWith('data:') && !now.startsWith('data:') ? now : then);
|
|
32
|
+
|
|
33
|
+
/** An image was uploaded: every undo step that still holds its local copy takes the stored address instead. */
|
|
34
|
+
export function imageStored(pageId: string, local: string, ref: string): void {
|
|
35
|
+
const h = hist[pageId];
|
|
36
|
+
if (!h) return;
|
|
37
|
+
for (const s of [...h.u, ...h.r]) for (const u of s.imgs) if (u.src === local) u.src = ref;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
export function pushHistory(): void {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { page } from './store';
|
|
2
|
+
import type { Page, Underlay } from './types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
A page's photos and plans: a list, drawn in order (the last on top), each placed, scaled, faded and locked on its
|
|
6
|
+
own. Pages saved when a page held one image (`underlay`) read as a list of that one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type Stored = { underlays?: Underlay[] | null; underlay?: (Omit<Underlay, 'id'> & { id?: string }) | null };
|
|
10
|
+
|
|
11
|
+
/** A stored page's images as a list (an older page's single image becomes the first), each with an id. */
|
|
12
|
+
export function imagesOf(doc: Stored): Underlay[] {
|
|
13
|
+
// (a host that reads a missing list as empty still finds an older page's one image)
|
|
14
|
+
const list = doc.underlays?.length ? doc.underlays : doc.underlay ? [doc.underlay] : [];
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
return list.filter(Boolean).map((u, i) => {
|
|
17
|
+
let id = u.id || 'img' + (i + 1);
|
|
18
|
+
while (seen.has(id)) id += 'x';
|
|
19
|
+
seen.add(id);
|
|
20
|
+
return { ...u, id };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Bring a page read from storage up to date: its images as a list. */
|
|
25
|
+
export function normalizeImages<P extends Stored>(pg: P): P & { underlays: Underlay[] } {
|
|
26
|
+
const out = pg as P & { underlays: Underlay[] };
|
|
27
|
+
out.underlays = imagesOf(pg);
|
|
28
|
+
delete out.underlay;
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The current page's images, bottom first. */
|
|
33
|
+
export const images = (pg: Page = page()): Underlay[] => pg.underlays;
|
|
34
|
+
/** One of the current page's images. */
|
|
35
|
+
export const imageById = (id: string, pg: Page = page()): Underlay | undefined => pg.underlays.find(u => u.id === id);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { allCons } from './faces';
|
|
2
|
+
import { sessionState } from './session';
|
|
3
|
+
import type { LinkInfo, LinkTarget, Note, Project, Sketch } from './types';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Links: tiles on the drawing for the job's files and measurements (notes of type 'link'), and the job measurements
|
|
7
|
+
that drive dimensions (a constraint's measurementRef). What's linked belongs to the job, which the host has: the
|
|
8
|
+
drawing keeps where each one points and what it was when it was linked, and asks the host for what it is now (see
|
|
9
|
+
editor/links.ts).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Micrometres in an inch: the job keeps lengths in µm, the drawing in inches. */
|
|
13
|
+
export const UM_PER_IN = 25400;
|
|
14
|
+
export const toUm = (inches: number): number => inches * UM_PER_IN;
|
|
15
|
+
export const fromUm = (um: number): number => um / UM_PER_IN;
|
|
16
|
+
|
|
17
|
+
/** A link's key, the same for the host and the editor: its kind, its group and its id. */
|
|
18
|
+
export const linkKey = (t: Pick<LinkTarget, 'kind' | 'groupId' | 'id'>): string => t.kind + ':' + t.groupId + ':' + t.id;
|
|
19
|
+
|
|
20
|
+
/** Whether `n` is a link's tile. */
|
|
21
|
+
export const isLink = (n: Note): n is Note & { link: LinkTarget } => n.type === 'link' && !!n.link;
|
|
22
|
+
|
|
23
|
+
/** How big a tile is on the screen as it's placed (points): its side on the drawing is what that is at the zoom then. */
|
|
24
|
+
export const LINK_TILE_PX = 96;
|
|
25
|
+
/** How big a tile can be on the drawing, inches; unset (never, once placed), this. */
|
|
26
|
+
export const LINK_MIN_IN = 6, LINK_MAX_IN = 480, LINK_SIDE_IN = 24;
|
|
27
|
+
/**
|
|
28
|
+
* On the screen, points: from LINK_FULL_PX across a tile shows its picture or icon and its name; under LINK_PIN_PX it's
|
|
29
|
+
* a pin of that size, however far out the drawing is.
|
|
30
|
+
*/
|
|
31
|
+
export const LINK_FULL_PX = 64, LINK_PIN_PX = 24;
|
|
32
|
+
/** A new link's leader colour: the palette's blue. */
|
|
33
|
+
export const LINK_COLOR = '#0364F4';
|
|
34
|
+
|
|
35
|
+
/** A tile's side on the drawing, inches. */
|
|
36
|
+
export const linkSide = (n: Note): number => n.ts ?? LINK_SIDE_IN;
|
|
37
|
+
|
|
38
|
+
/** What the host has said each link is now, by key (asked for by editor/links.ts; nothing until it answers). */
|
|
39
|
+
export const linkInfo = sessionState(() => ({ byKey: {} as Record<string, LinkInfo> }));
|
|
40
|
+
export const linkInfoOf = (t: LinkTarget): LinkInfo | undefined => linkInfo.byKey[linkKey(t)];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What the drawing keeps of a link the host picked: where it points, and what it was then — shown until the host says
|
|
44
|
+
* what it is now, in exports, and once it's gone. (Nothing else the host put on it.)
|
|
45
|
+
*/
|
|
46
|
+
export function linkSnapshot(t: LinkTarget): LinkTarget {
|
|
47
|
+
const out: LinkTarget = { kind: t.kind, id: t.id, groupId: t.groupId };
|
|
48
|
+
for (const k of ['columnId', 'path', 'name', 'groupName', 'fileType', 'mkind', 'value', 'text'] as const) {
|
|
49
|
+
if (t[k] != null) (out as unknown as Record<string, unknown>)[k] = t[k];
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What a link is called: what the host says, or what it was when linked. */
|
|
55
|
+
export function linkName(t: LinkTarget): string {
|
|
56
|
+
return linkInfoOf(t)?.name ?? t.name ?? (t.kind === 'file' ? 'File' : 'Measurement');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A sketch's links: its tiles, and its dimensions' measurements (on its wall faces too). */
|
|
60
|
+
export function sketchLinks(sk: Sketch): LinkTarget[] {
|
|
61
|
+
const out: LinkTarget[] = [];
|
|
62
|
+
for (const n of sk.notes) if (isLink(n)) out.push(n.link);
|
|
63
|
+
for (const [, c] of allCons(sk)) if (c.measurementRef) out.push(c.measurementRef);
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Everything a project links to, each once: its pages' tiles and the measurements its dimensions show. */
|
|
68
|
+
export function linkTargets(p: Project): LinkTarget[] {
|
|
69
|
+
const out = new Map<string, LinkTarget>();
|
|
70
|
+
for (const pg of p.pages) for (const t of sketchLinks(pg.sk)) if (!out.has(linkKey(t))) out.set(linkKey(t), t);
|
|
71
|
+
return [...out.values()];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The job measurements a project links to, for the host's record of where measurements are placed. */
|
|
75
|
+
export function linkedMeasurements(p: Project): { groupId: string; id: string; columnId?: string }[] {
|
|
76
|
+
return linkTargets(p).filter(t => t.kind === 'measurement').map(t => ({ groupId: t.groupId, id: t.id, ...(t.columnId ? { columnId: t.columnId } : {}) }));
|
|
77
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Note, NoteCap, NoteType } from './types';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Markup's look, as BOLDR's annotation editor offers it: a color from its palette, a stroke width, solid / dashed /
|
|
5
|
+
dotted, and for open strokes (pen, arrows) how each end finishes. Widths are screen pixels: markup reads the same at
|
|
6
|
+
any zoom.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** BOLDR's annotation palette (the annotation editors' 15 colors, five to a row). */
|
|
10
|
+
export const NOTE_COLORS = [
|
|
11
|
+
'#FFFFFF', '#000000', '#0364F4', '#58A942', '#FFEA00',
|
|
12
|
+
'#FF8C00', '#FF0073', '#FF0000', '#FF66C7', '#780A9F',
|
|
13
|
+
'#225913', '#4D2409', '#CEB18E', '#182E39', '#680B19',
|
|
14
|
+
];
|
|
15
|
+
export const DEFAULT_NOTE_COLOR = '#FF0000';
|
|
16
|
+
/** stroke widths offered, thinnest first */
|
|
17
|
+
export const NOTE_WIDTHS = [1, 2, 3, 5, 8, 12];
|
|
18
|
+
export const DEFAULT_NOTE_WIDTH = 3;
|
|
19
|
+
export type NoteStyle = 'solid' | 'dash' | 'dot';
|
|
20
|
+
export const NOTE_STYLES: NoteStyle[] = ['solid', 'dash', 'dot'];
|
|
21
|
+
export const NOTE_CAPS: NoteCap[] = ['butt', 'round', 'arrow'];
|
|
22
|
+
|
|
23
|
+
/** Open strokes: the kinds whose ends can be finished (round, flat or an arrowhead) — a link's leader among them. */
|
|
24
|
+
export const hasEnds = (n: Note): boolean => n.type === 'pen' || n.type === 'arrow' || n.type === 'line' || (n.type === 'link' && !!n.leader);
|
|
25
|
+
|
|
26
|
+
/** Two-point strokes: a straight line between `pts[0]` and `pts[1]`, reshaped by moving either end. */
|
|
27
|
+
export const isSegment = (n: Note): boolean => n.type === 'arrow' || n.type === 'line';
|
|
28
|
+
|
|
29
|
+
/** Outlined shapes in the box `pts[0]`–`pts[1]`: drawn, hit and exported by their outline (shapeOutline). */
|
|
30
|
+
export const CLOSED_SHAPES: ReadonlySet<NoteType> = new Set(['rect', 'triangle', 'ellipse']);
|
|
31
|
+
|
|
32
|
+
type Pt = [number, number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A closed shape's outline, in world coordinates (y up), fitted to the box its two corners make: a rectangle, a
|
|
36
|
+
* triangle with its point at the top, or a circle (an ellipse, when the box isn't square). Null for anything else.
|
|
37
|
+
*/
|
|
38
|
+
export function shapeOutline(n: Note): Pt[] | null {
|
|
39
|
+
if (!CLOSED_SHAPES.has(n.type) || !n.pts || n.pts.length < 2) return null;
|
|
40
|
+
const [[ax, ay], [bx, by]] = n.pts;
|
|
41
|
+
const l = Math.min(ax, bx), r = Math.max(ax, bx), b = Math.min(ay, by), t = Math.max(ay, by);
|
|
42
|
+
/** a point of the box, u and v from 0 to 1 (left to right, bottom to top) */
|
|
43
|
+
const at = (u: number, v: number): Pt => [l + (r - l) * u, b + (t - b) * v];
|
|
44
|
+
if (n.type === 'rect') return [at(0, 1), at(1, 1), at(1, 0), at(0, 0)];
|
|
45
|
+
if (n.type === 'triangle') return [at(0.5, 1), at(1, 0), at(0, 0)];
|
|
46
|
+
return Array.from({ length: 64 }, (_, i) => { const a = (i / 64) * 2 * Math.PI; return at(0.5 + Math.cos(a) / 2, 0.5 + Math.sin(a) / 2); });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface NoteLook { color: string; width: number; style: NoteStyle; startCap: NoteCap; endCap: NoteCap }
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* How a note is drawn, its unset parts filled in. A link's leader starts under its tile (flat) and points at what it's
|
|
53
|
+
* for (an arrowhead) — its end is the far one, unlike Skia's, where the arrow at the target needed the start cap.
|
|
54
|
+
*/
|
|
55
|
+
export function noteLook(n: Note): NoteLook {
|
|
56
|
+
return {
|
|
57
|
+
color: n.color,
|
|
58
|
+
width: n.width ?? (n.type === 'cloud' ? 2.5 : DEFAULT_NOTE_WIDTH),
|
|
59
|
+
style: n.style ?? 'solid',
|
|
60
|
+
startCap: n.startCap ?? (n.type === 'link' ? 'butt' : 'round'),
|
|
61
|
+
endCap: n.cap ?? (n.type === 'arrow' || n.type === 'link' ? 'arrow' : 'round'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The dash pattern for a style at a width (screen pixels); [] when solid. Dots are zero-length dashes with round caps. */
|
|
66
|
+
export function dashFor(style: NoteStyle, width: number): number[] {
|
|
67
|
+
if (style === 'dash') return [width * 2.5 + 5, width * 1.5 + 4];
|
|
68
|
+
if (style === 'dot') return [0, width * 1.5 + 3];
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Text notes: set at one size (NOTE_TEXT_SIZE px) and scaled to their size on the drawing — letters `ts` inches tall,
|
|
74
|
+
* NOTE_TEXT_IN unless set, from NOTE_TEXT_MIN_IN to NOTE_TEXT_MAX_IN — so a note zooms with the drawing.
|
|
75
|
+
*/
|
|
76
|
+
export const NOTE_TEXT_SIZE = 14, NOTE_TEXT_IN = 5, NOTE_TEXT_MIN_IN = 1, NOTE_TEXT_MAX_IN = 120;
|
|
77
|
+
/** A note's letter height on the drawing, inches (a note from before kept screen pixels: 14 px is 5", as printed). */
|
|
78
|
+
export const noteTextIn = (n: Note): number => n.ts ?? (n.size != null ? (n.size * NOTE_TEXT_IN) / NOTE_TEXT_SIZE : NOTE_TEXT_IN);
|
|
79
|
+
/** Text sizes the markup panel offers (letter height on the drawing, inches); any other comes from a note's corners. */
|
|
80
|
+
export const NOTE_TEXT_SIZES = [3, 5, 8, 12];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CadMeasurementValue } from '../data/provider';
|
|
2
|
+
import type { Constraint } from './types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
A dimension's value as BOLDR keeps a measurement — lengths in micrometres, angles in degrees — and back: for the
|
|
6
|
+
drawing's own measurements (data/sync.ts) and for measurements of the job it's linked to (editor/linkWrite.ts).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const LENGTHS = new Set<Constraint['type']>(['dist', 'pldist', 'diam']);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dimension `c`'s value (or `value`, one it's about to have) as a measurement. Null for what isn't one: reference and
|
|
13
|
+
* print dimensions, measured-only ones, and constraints without a value.
|
|
14
|
+
*/
|
|
15
|
+
export function dimMeasurement(c: Constraint, value = c.value): CadMeasurementValue | null {
|
|
16
|
+
if (c.driving === false || c.layer === 'print' || value == null || c.auto) return null;
|
|
17
|
+
if (c.type === 'angle') return { kind: 'angle', value: Math.round(value * 100) / 100 };
|
|
18
|
+
// a fillet's radius is kept as its diameter; BOLDR keeps the radius
|
|
19
|
+
if (LENGTHS.has(c.type)) return { kind: 'length', value: Math.round((c.rad ? value / 2 : value) * 25400) };
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A measurement's value as dimension `c` holds it: inches (a fillet's radius as its diameter), or degrees. */
|
|
24
|
+
export function dimValueOf(c: Constraint, m: { value: number }): number {
|
|
25
|
+
return c.type === 'angle' ? m.value : (m.value / 25400) * (c.rad ? 2 : 1);
|
|
26
|
+
}
|
package/src/model/openings.ts
CHANGED
|
@@ -307,3 +307,9 @@ export function pickBetween(sk: Sketch, a: string, aEdge: OpeningEdge, b: string
|
|
|
307
307
|
const cb = driveCheck(sk, b, toB.ref), ca = driveCheck(sk, a, toA.ref);
|
|
308
308
|
return cb === 'free' ? toB : ca === 'free' ? toA : cb === 'reroot' ? toB : ca === 'reroot' ? toA : toB;
|
|
309
309
|
}
|
|
310
|
+
|
|
311
|
+
/** A new door's or window's width, inches. */
|
|
312
|
+
export const STANDARD_OPENING_W = 36;
|
|
313
|
+
|
|
314
|
+
/** Whether an opening's width was entered (blue), rather than the standard size it was placed at (grey). */
|
|
315
|
+
export const widthEntered = (o: Opening): boolean => o.wSet ?? Math.abs(o.w - STANDARD_OPENING_W) > 1e-6;
|