@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
package/src/editor/keypad.ts
CHANGED
|
@@ -8,15 +8,25 @@ import { closeKeypad, refreshKeypad, type KeypadSpec } from './ports';
|
|
|
8
8
|
("fresh"), the fraction and ( ) keys, the line under the display, laser readings, and reading the value on Apply.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
/** `error`: why Apply was refused, shown until the next edit. */
|
|
12
|
-
interface Kp {
|
|
11
|
+
/** `error`: why Apply was refused, shown until the next edit. `host`: the host's own keypad is showing it. */
|
|
12
|
+
interface Kp {
|
|
13
|
+
spec: KeypadSpec; text: string; fresh: boolean; error?: string; host?: boolean;
|
|
14
|
+
/** the last laser reading's record (laserReading's capture), and what it put in the display: kept while that's unchanged */
|
|
15
|
+
capture?: { of: unknown; text: string };
|
|
16
|
+
}
|
|
13
17
|
const kps = sessionState(() => ({ cur: null as Kp | null }));
|
|
14
18
|
|
|
15
19
|
export const FRACTIONS = ['1/16', '1/8', '1/4', '3/8', '1/2', '5/8', '3/4', '7/8'];
|
|
16
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Key layouts, phone-style (1 2 3 on top): '⌫' and '( )' are special keys. In lengths the 0 is three keys wide
|
|
22
|
+
* (`keySpan`); the fraction keys put their own space before them, so there's no space key. (A laser reading goes
|
|
23
|
+
* straight into an open keypad: no key for it.)
|
|
24
|
+
*/
|
|
17
25
|
export const keyLayout = (numeric?: boolean): string[] => numeric
|
|
18
|
-
? ['
|
|
19
|
-
: ['
|
|
26
|
+
? ['1', '2', '3', '⌫', '4', '5', '6', '-', '7', '8', '9', '+', '0', '.', '/', '( )']
|
|
27
|
+
: ['1', '2', '3', '⌫', '4', '5', '6', '"', '7', '8', '9', "'", '0', '.'];
|
|
28
|
+
/** How many columns (of four) a key takes. */
|
|
29
|
+
export const keySpan = (k: string, numeric?: boolean): number => (k === '0' && !numeric ? 3 : 1);
|
|
20
30
|
|
|
21
31
|
export const keypadOpen = (): boolean => !!kps.cur;
|
|
22
32
|
export const keypadSpec = (): KeypadSpec | null => kps.cur?.spec ?? null;
|
|
@@ -25,10 +35,14 @@ export const keypadFresh = (): boolean => !!kps.cur?.fresh;
|
|
|
25
35
|
/** The fraction row shows for lengths in feet and inches, not for degrees or millimetres. */
|
|
26
36
|
export const fractionRowShown = (): boolean => !(kps.cur?.spec.numeric || settings.unit === 'mm');
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
/** Open the keypad for `spec`; `host`: the host shows its own keypad for it (see react/hostKeypad.ts). */
|
|
39
|
+
export function keypadStart(spec: KeypadSpec, host = false): void {
|
|
40
|
+
kps.cur = { spec, text: spec.initial || '', fresh: !!spec.selectAll, host };
|
|
30
41
|
}
|
|
31
42
|
|
|
43
|
+
/** Whether the open keypad is the host's own. */
|
|
44
|
+
export const keypadOnHost = (): boolean => !!kps.cur?.host;
|
|
45
|
+
|
|
32
46
|
/** Close the keypad; returns what it was opened with. */
|
|
33
47
|
export function keypadEnd(): KeypadSpec | null {
|
|
34
48
|
const spec = kps.cur?.spec ?? null;
|
|
@@ -89,14 +103,23 @@ export function keypadSetReading(v: number): void {
|
|
|
89
103
|
kps.cur.error = undefined;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
106
|
+
/**
|
|
107
|
+
* A laser reading into an open length keypad. False if this keypad can't take one (degrees, or laser turned off).
|
|
108
|
+
* The host's own keypad can't be typed into from here, so a reading there is the value: it's applied at once.
|
|
109
|
+
* `capture`, the host's record of the reading, goes with the value if it's applied as it came in (see KeypadSpec.onDone).
|
|
110
|
+
*/
|
|
111
|
+
export function keypadTakeLaser(v: number, capture?: unknown): boolean {
|
|
94
112
|
const k = kps.cur;
|
|
95
113
|
if (!k || k.spec.laser === false || k.spec.numeric) return false;
|
|
114
|
+
if (k.host) { keypadSubmit(v, capture); return true; }
|
|
96
115
|
keypadSetReading(v);
|
|
116
|
+
k.capture = capture !== undefined ? { of: capture, text: k.text } : undefined;
|
|
97
117
|
return true;
|
|
98
118
|
}
|
|
99
119
|
|
|
120
|
+
/** The last reading's record, if the display still shows what it put there. */
|
|
121
|
+
const readingCapture = (k: Kp): unknown => (k.capture && k.capture.text === k.text ? k.capture.of : undefined);
|
|
122
|
+
|
|
100
123
|
const parse = (k: Kp): number | null => (k.spec.numeric ? parseNum(k.text) : parseLen(k.text));
|
|
101
124
|
|
|
102
125
|
/** The value currently typed, or null if none/unreadable (for live previews on the canvas). */
|
|
@@ -123,6 +146,15 @@ export function keypadValue(): { v: number } | { error: string } {
|
|
|
123
146
|
return v == null ? { error: 'Enter a length like 12\' 6" or 150.5' } : { v };
|
|
124
147
|
}
|
|
125
148
|
|
|
149
|
+
/** A value from the host's own keypad (or a laser reading into it): close the keypad and hand it on, as Apply does. */
|
|
150
|
+
export function keypadSubmit(v: number, capture?: unknown): void {
|
|
151
|
+
const k = kps.cur;
|
|
152
|
+
if (!k) return;
|
|
153
|
+
const done = k.spec.onDone;
|
|
154
|
+
closeKeypad();
|
|
155
|
+
if (done) done(v, capture);
|
|
156
|
+
}
|
|
157
|
+
|
|
126
158
|
/**
|
|
127
159
|
* Apply: close the keypad and hand the value to whoever opened it — or, if it can't be read, keep the keypad open
|
|
128
160
|
* and say so under the display.
|
|
@@ -132,7 +164,7 @@ export function keypadApply(): void {
|
|
|
132
164
|
if (!k) return;
|
|
133
165
|
const r = keypadValue();
|
|
134
166
|
if ('error' in r) { k.error = r.error; refreshKeypad(); return; }
|
|
135
|
-
const done = k.spec.onDone;
|
|
167
|
+
const done = k.spec.onDone, capture = readingCapture(k);
|
|
136
168
|
closeKeypad();
|
|
137
|
-
if (done) done(r.v);
|
|
169
|
+
if (done) done(r.v, capture);
|
|
138
170
|
}
|
package/src/editor/keys.ts
CHANGED
|
@@ -5,9 +5,14 @@ import { saveSoon } from '../model/store';
|
|
|
5
5
|
import { fitView, requestDraw } from '../render/view';
|
|
6
6
|
import { cancelTool, setTool, tools } from '../tools/registry';
|
|
7
7
|
import { applyCtxAction, deleteSelection } from './actions';
|
|
8
|
+
import { addLinks, linkPlusVisible, openSelectedLink, selectedLink } from './linkActions';
|
|
8
9
|
import { keys } from './gestures';
|
|
9
10
|
import { keypadApply, keypadDeleteLast, keypadInsert, keypadOpen } from './keypad';
|
|
10
|
-
import {
|
|
11
|
+
import { confirmCancel, confirmChoose, confirmOpen } from './confirm';
|
|
12
|
+
import { toggleConstr, toggleWalls } from './mode';
|
|
13
|
+
import { closeElevation, elevOpen, WALL_KEYS } from './elevation';
|
|
14
|
+
import { key3d } from './view3d';
|
|
15
|
+
import { is3d } from './view3dState';
|
|
11
16
|
import {
|
|
12
17
|
closeKeypad, dismissConflict, dismissPopovers, refreshKeypad, saveNowPort, setCursor, toast, updateCtx, updateHint,
|
|
13
18
|
} from './ports';
|
|
@@ -16,20 +21,28 @@ import {
|
|
|
16
21
|
Keyboard shortcuts for the plan, whatever delivers the keys (DOM key events, a hardware keyboard on a tablet).
|
|
17
22
|
While the keypad is open, keys type into it. Otherwise: ⌘Z / ⇧⌘Z / ⌘Y undo and redo, ⌘S saves, Esc steps back one
|
|
18
23
|
level at a time, Delete removes the selection, ⇧ + a letter adds a constraint, single letters pick tools, F fits,
|
|
19
|
-
G toggles grid snap, X
|
|
24
|
+
G toggles grid snap, W walls, X construction lines, and Space held turns a drag into a pan. In Annotate, + adds files
|
|
25
|
+
and measurements of the job and Enter opens the selected one. In the 3D tab: F frames everything and Esc lets go of
|
|
26
|
+
what's tapped.
|
|
20
27
|
*/
|
|
21
28
|
|
|
22
29
|
/** A key press: `key` as in KeyboardEvent.key; `mod` is ⌘ or Ctrl. */
|
|
23
30
|
export interface KeyIn { key: string; shift: boolean; mod: boolean; alt: boolean }
|
|
24
31
|
|
|
25
32
|
const TOOLKEYS: Record<Mode, Record<string, string>> = {
|
|
26
|
-
draft: { v: 'select', l: 'line', s: 'freehand',
|
|
27
|
-
annotate: { v: 'aselect', k: 'arrow', p: 'pen', n: 'text', q: 'cloud', j: 'calib' },
|
|
33
|
+
draft: { v: 'select', l: 'line', s: 'freehand', r: 'rect', c: 'circle', a: 'arc', d: 'dim', t: 'trim', o: 'offset', b: 'door', n: 'window', m: 'room' },
|
|
34
|
+
annotate: { v: 'aselect', l: 'mline', k: 'arrow', r: 'mrect', c: 'mcircle', p: 'pen', n: 'text', q: 'cloud', j: 'calib' },
|
|
28
35
|
};
|
|
29
36
|
const SHIFT_CONS: Record<string, string> = { H: 'h', V: 'v', P: 'par', R: 'perp', L: 'col', T: 'tan', E: 'eq', M: 'mid', O: 'online', F: 'lock' };
|
|
30
37
|
|
|
31
38
|
/** Handle a key press on the plan. Returns true when the key was used, so the host should stop its default action. */
|
|
32
39
|
export function keyDown(e: KeyIn): boolean {
|
|
40
|
+
// a question up: Enter is its first choice, Escape closes it
|
|
41
|
+
if (confirmOpen()) {
|
|
42
|
+
if (e.key === 'Enter') confirmChoose(0);
|
|
43
|
+
else if (e.key === 'Escape') confirmCancel();
|
|
44
|
+
return !e.mod;
|
|
45
|
+
}
|
|
33
46
|
if (ed.readOnly) {
|
|
34
47
|
// view only: fit, and Space to pan
|
|
35
48
|
if (e.key === ' ' && !keys.spaceDown) { keys.spaceDown = true; setCursor('grab'); return true; }
|
|
@@ -50,6 +63,8 @@ export function keyDown(e: KeyIn): boolean {
|
|
|
50
63
|
if (e.mod && k === 'y') { redo(); return true; }
|
|
51
64
|
if (e.mod && k === 's') { saveNowPort(); return true; }
|
|
52
65
|
if (e.mod) return false;
|
|
66
|
+
// (the 3D tab has no tools)
|
|
67
|
+
if (is3d()) return key3d(e);
|
|
53
68
|
const t = tools[ed.tool];
|
|
54
69
|
if (t && t.key && t.key(e)) return true;
|
|
55
70
|
// Esc steps back one level at a time: conflict sheet → popover → in-progress step + selection → the Select tool
|
|
@@ -64,6 +79,7 @@ export function keyDown(e: KeyIn): boolean {
|
|
|
64
79
|
}
|
|
65
80
|
const home = ed.mode === 'draft' ? 'select' : 'aselect';
|
|
66
81
|
if (ed.tool !== home) setTool(home);
|
|
82
|
+
else if (elevOpen()) closeElevation(); // the wall view: back to the plan
|
|
67
83
|
return false;
|
|
68
84
|
}
|
|
69
85
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
@@ -71,6 +87,9 @@ export function keyDown(e: KeyIn): boolean {
|
|
|
71
87
|
deleteSelection();
|
|
72
88
|
return true;
|
|
73
89
|
}
|
|
90
|
+
// Annotate: + adds files and measurements (the + by the rail), Enter opens the selected link
|
|
91
|
+
if ((e.key === '+' || e.key === '=') && linkPlusVisible()) { void addLinks(); return true; }
|
|
92
|
+
if (e.key === 'Enter' && ed.mode === 'annotate' && selectedLink()) { openSelectedLink(); return true; }
|
|
74
93
|
if (e.shift) {
|
|
75
94
|
const c = SHIFT_CONS[e.key.toUpperCase()];
|
|
76
95
|
if (c && ed.mode === 'draft') { applyCtxAction(c); return true; }
|
|
@@ -79,7 +98,8 @@ export function keyDown(e: KeyIn): boolean {
|
|
|
79
98
|
if (k === 'f') { fitView(); return false; }
|
|
80
99
|
if (k === 'g') { settings.snapGrid = !settings.snapGrid; toast('Snap to grid ' + (settings.snapGrid ? 'on' : 'off')); updateCtx(); saveSoon(); return false; }
|
|
81
100
|
if (k === 'x' && ed.mode === 'draft') { toggleConstr(); toast(ed.drawConstr ? 'Drawing construction lines' : 'Drawing regular lines'); return false; }
|
|
82
|
-
|
|
101
|
+
if (k === 'w' && ed.mode === 'draft' && !elevOpen()) { toggleWalls(); toast(ed.drawWalls ? 'Drawing walls' : 'Drawing lines'); return false; }
|
|
102
|
+
const tk = (elevOpen() ? WALL_KEYS : TOOLKEYS[ed.mode])[k];
|
|
83
103
|
if (tk) setTool(tk);
|
|
84
104
|
return false;
|
|
85
105
|
}
|
package/src/editor/laser.ts
CHANGED
|
@@ -3,9 +3,13 @@ import { ed } from '../app/state';
|
|
|
3
3
|
import { dropHistory, pushHistory } from '../model/history';
|
|
4
4
|
import { uid } from '../model/ids';
|
|
5
5
|
import { sessionState } from '../model/session';
|
|
6
|
+
import { measure } from '../model/constraints';
|
|
7
|
+
import { defaultFace, edgeLabel, edgeOf, wallOf } from '../model/planGeom';
|
|
6
8
|
import { lengthDimOf, lineLen } from '../model/sketch';
|
|
7
9
|
import { SK } from '../model/store';
|
|
8
|
-
import {
|
|
10
|
+
import { faceLength } from '../model/wallfaces';
|
|
11
|
+
import { V } from '../render/view';
|
|
12
|
+
import { LENGTH_SET_ALREADY, setDimValue, type DimValueOpts } from '../tools/dim';
|
|
9
13
|
import { fmtLen } from '../units';
|
|
10
14
|
import { keypadTakeLaser } from './keypad';
|
|
11
15
|
import { refreshKeypad, refreshTray, toast } from './ports';
|
|
@@ -16,33 +20,101 @@ import { selInfo } from './selection';
|
|
|
16
20
|
keypad, onto the one selected line, or to the tray, where it can be dragged onto a line later.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
/** A reading, and the host's record of it (laserReading's `capture`), kept with it in the tray. */
|
|
24
|
+
export interface Reading { id: string; v: number; capture?: unknown }
|
|
20
25
|
/** `external`: a real measure the host app is connected to (otherwise the harness's simulated one). */
|
|
21
26
|
export const laser = sessionState(() => ({ on: false, external: false, readings: [] as Reading[] }));
|
|
22
27
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
/** What a new dimension takes from DimValueOpts: its link, and the reading it's from. */
|
|
29
|
+
const linkFields = (o: DimValueOpts) => ({ ...(o.link ? { measurementRef: o.link } : {}), ...(o.capture !== undefined && !o.link ? { capture: o.capture } : {}) });
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set a line's length: edit its length dimension, or add a driving one. A wall's is measured on `side`, the edge picked
|
|
33
|
+
* — or, picked whole, where the + measures it (the side a space is on, else its centerline). `opts`: a measurement of
|
|
34
|
+
* the job it's linked to, the laser reading it's from (see setDimValue). Whether it took (false: refused, or asked).
|
|
35
|
+
*/
|
|
36
|
+
export function setLineLength(lid: string, v: number, side?: 1 | -1, opts: DimValueOpts = {}): boolean {
|
|
37
|
+
const sk = SK(), l = sk.lines[lid];
|
|
38
|
+
if (!l) return false;
|
|
39
|
+
const wall = l.kind === 'wall' && !l.constr;
|
|
26
40
|
const ex = lengthDimOf(sk, lid);
|
|
27
|
-
if (ex)
|
|
28
|
-
const
|
|
41
|
+
if (ex) return setDimValue(ex, v, wall && side ? { face: side } : undefined, opts);
|
|
42
|
+
const face = wall ? side ?? defaultFace(sk, lid, ed.wallTap?.id === lid ? ed.wallTap.side : undefined) : undefined;
|
|
29
43
|
pushHistory();
|
|
30
|
-
const r = tryAddConstraint({ type: 'dist', a: l.a, b: l.b, kind: 'al', value: v, line: lid, driving: true, off: 0 });
|
|
44
|
+
const r = tryAddConstraint({ type: 'dist', a: l.a, b: l.b, kind: 'al', value: v, line: lid, driving: true, off: face ? (face * 30) / V().s : 0, ...(face ? { face } : {}), ...linkFields(opts) });
|
|
45
|
+
// a measurement of the job drives a line, or nothing: not one whose length the drawing already sets
|
|
46
|
+
if (r.ok && r.ref && opts.link) {
|
|
47
|
+
delete sk.cons[r.id!];
|
|
48
|
+
dropHistory();
|
|
49
|
+
toast(LENGTH_SET_ALREADY, { warn: true });
|
|
50
|
+
afterEdit();
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
31
53
|
if (!r.ok) dropHistory();
|
|
32
54
|
afterEdit();
|
|
55
|
+
return r.ok;
|
|
33
56
|
}
|
|
34
57
|
|
|
35
|
-
/** A
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
/** A circle's or arc's diameter dimension (a fillet's radius), if it has one. */
|
|
59
|
+
const diamDimOf = (id: string): string | undefined => { const sk = SK(); return Object.keys(sk.cons).find(k => sk.cons[k].type === 'diam' && sk.cons[k].c === id); };
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set a circle's or arc's diameter: edit its diameter dimension (a fillet's radius, as the radius), or add a driving
|
|
63
|
+
* one. A curved wall's is measured across `side`, the edge picked — or, picked whole, where the + measures it.
|
|
64
|
+
*/
|
|
65
|
+
export function setCurveDiameter(id: string, v: number, side?: 1 | -1, opts: DimValueOpts = {}): boolean {
|
|
66
|
+
const sk = SK();
|
|
67
|
+
if (!sk.circles[id] && !sk.arcs[id]) return false;
|
|
68
|
+
const wall = !!wallOf(sk, id), ex = diamDimOf(id);
|
|
69
|
+
if (ex) return setDimValue(ex, sk.cons[ex].rad ? v * 2 : v, wall && side ? { face: side } : undefined, opts);
|
|
70
|
+
const face = wall ? side ?? defaultFace(sk, id, ed.wallTap?.id === id ? ed.wallTap.side : undefined) : undefined;
|
|
71
|
+
pushHistory();
|
|
72
|
+
const r = tryAddConstraint({ type: 'diam', c: id, value: v, driving: true, ...(face ? { face } : {}), ...linkFields(opts) });
|
|
73
|
+
if (!r.ok) dropHistory();
|
|
74
|
+
afterEdit();
|
|
75
|
+
return r.ok;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What a reading goes on — a selected line, circle, arc or wall edge (or one it's dropped on): its id, and the edge's side. */
|
|
79
|
+
function readingTarget(id: string): [string, (1 | -1)?] {
|
|
80
|
+
const sk = SK();
|
|
81
|
+
return sk.lines[id] || sk.circles[id] || sk.arcs[id] ? [id] : edgeOf(id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A reading put on a line's (or straight wall's) length, or a circle's or arc's diameter. */
|
|
85
|
+
function setReading(id: string, v: number, side?: 1 | -1, capture?: unknown): void {
|
|
86
|
+
if (SK().lines[id]) setLineLength(id, v, side, { capture }); else setCurveDiameter(id, v, side, { capture });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* What a reading was put on, as the toast says it: 'the selected line', 'the inside edge of the wall', 'the Kitchen
|
|
91
|
+
* side of the wall', 'the circle across its inner edge'.
|
|
92
|
+
*/
|
|
93
|
+
function appliedTo(id: string): string {
|
|
94
|
+
const sk = SK(), l = sk.lines[id];
|
|
95
|
+
const kind = l ? (l.kind === 'wall' ? 'wall' : 'line') : sk.circles[id] ? 'circle' : 'arc';
|
|
96
|
+
const ex = l ? lengthDimOf(sk, id) : diamDimOf(id), face = ex ? sk.cons[ex].face : undefined;
|
|
97
|
+
if (!face) return 'the selected ' + kind;
|
|
98
|
+
const e = edgeLabel(sk, id, face), lower = e.endsWith(' edge') ? e[0].toLowerCase() + e.slice(1) : e;
|
|
99
|
+
return l ? 'the ' + lower + ' of the ' + kind : 'the ' + kind + ' across its ' + lower;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A reading arrived: into the keypad, onto the selected line or curve, or into the tray (newest first, at most 8).
|
|
104
|
+
* `capture` is the host's record of it (its device, its number), kept with the dimension it sets until that's saved
|
|
105
|
+
* to the group — or handed to the job with it, for one linked to a measurement there.
|
|
106
|
+
*/
|
|
107
|
+
export function applyReading(v: number, capture?: unknown): void {
|
|
108
|
+
if (keypadTakeLaser(v, capture)) { refreshKeypad(); return; }
|
|
109
|
+
const s = selInfo();
|
|
110
|
+
const one = s.n === 1 ? s.lines[0] ?? s.wfaces[0] ?? s.curves[0] : undefined;
|
|
111
|
+
if (ed.mode === 'draft' && one) {
|
|
112
|
+
const [id, side] = readingTarget(one);
|
|
113
|
+
setReading(id, v, side, capture);
|
|
114
|
+
toast('Laser ' + fmtLen(v) + ' applied to ' + appliedTo(id));
|
|
43
115
|
return;
|
|
44
116
|
}
|
|
45
|
-
laser.readings.unshift({ id: uid('m'), v });
|
|
117
|
+
laser.readings.unshift({ id: uid('m'), v, ...(capture !== undefined ? { capture } : {}) });
|
|
46
118
|
laser.readings = laser.readings.slice(0, 8);
|
|
47
119
|
refreshTray();
|
|
48
120
|
toast('Reading ' + fmtLen(v) + ' saved to the tray');
|
|
@@ -55,14 +127,18 @@ export function applyReading(v: number): void {
|
|
|
55
127
|
export function useTrayReading(id: string, dropOn?: string | null): void {
|
|
56
128
|
const r = laser.readings.find(x => x.id === id);
|
|
57
129
|
if (!r) return;
|
|
130
|
+
// (a line's id, or a wall edge's)
|
|
58
131
|
let target: string | null = dropOn ?? null;
|
|
59
132
|
if (dropOn === undefined) {
|
|
60
133
|
const s = selInfo();
|
|
61
134
|
if (s.lines.length === 1) target = s.lines[0];
|
|
62
|
-
else
|
|
135
|
+
else if (s.wfaces.length === 1) target = s.wfaces[0];
|
|
136
|
+
else if (s.curves.length === 1) target = s.curves[0];
|
|
137
|
+
else toast('Select a line first, or drag the reading onto one.', { warn: true });
|
|
63
138
|
}
|
|
64
139
|
if (target && ed.mode === 'draft') {
|
|
65
|
-
|
|
140
|
+
const [id, side] = readingTarget(target);
|
|
141
|
+
setReading(id, r.v, side, r.capture);
|
|
66
142
|
laser.readings = laser.readings.filter(x => x !== r);
|
|
67
143
|
refreshTray();
|
|
68
144
|
toast(fmtLen(r.v) + ' applied');
|
|
@@ -76,7 +152,7 @@ export function useTrayReading(id: string, dropOn?: string | null): void {
|
|
|
76
152
|
export function keypadLaserReading(): number | null {
|
|
77
153
|
if (!laser.external) return simulatedReading();
|
|
78
154
|
const r = laser.readings.shift();
|
|
79
|
-
if (!r) { toast('Take a reading with the laser: it goes straight into this keypad.'); return null; }
|
|
155
|
+
if (!r) { toast('Take a reading with the laser: it goes straight into this keypad.', { warn: true }); return null; }
|
|
80
156
|
refreshTray();
|
|
81
157
|
return r.v;
|
|
82
158
|
}
|
|
@@ -86,5 +162,7 @@ export function simulatedReading(): number {
|
|
|
86
162
|
const s = selInfo(), sk = SK();
|
|
87
163
|
let base = 48 + Math.random() * 168;
|
|
88
164
|
if (s.lines.length === 1) base = lineLen(sk, sk.lines[s.lines[0]]);
|
|
165
|
+
else if (s.wfaces.length === 1) { const [id, side] = edgeOf(s.wfaces[0]); base = sk.lines[id] ? faceLength(sk, id, side) : measure(sk, { type: 'diam', c: id, face: side }); }
|
|
166
|
+
else if (s.curves.length === 1) base = measure(sk, { type: 'diam', c: s.curves[0] });
|
|
89
167
|
return Math.max(1, Math.round((base + (Math.random() - 0.5) * 2.2) * 16) / 16);
|
|
90
168
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { afterEdit } from '../app/edit';
|
|
2
|
+
import { ed } from '../app/state';
|
|
3
|
+
import { clamp } from '../model/geom';
|
|
4
|
+
import { pushHistory } from '../model/history';
|
|
5
|
+
import { uid } from '../model/ids';
|
|
6
|
+
import { isLink, LINK_COLOR, LINK_MAX_IN, LINK_MIN_IN, LINK_TILE_PX, linkInfoOf, linkName, linkSide, linkSnapshot } from '../model/links';
|
|
7
|
+
import { dimMeasurement } from '../model/measurement';
|
|
8
|
+
import { linkAt } from '../render/hit';
|
|
9
|
+
import { askConfirm } from './confirm';
|
|
10
|
+
import { SK, store } from '../model/store';
|
|
11
|
+
import type { LinkTarget, Note } from '../model/types';
|
|
12
|
+
import { cvs, linkBox, mainT, requestDraw, s2w } from '../render/view';
|
|
13
|
+
import { setTool } from '../tools/registry';
|
|
14
|
+
import { elevOpen } from './elevation';
|
|
15
|
+
import { is3d } from './view3dState';
|
|
16
|
+
import { canLink, linksHost, openLink, pickLinks } from './links';
|
|
17
|
+
import { toast } from './ports';
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
Linking. In Annotate the + puts the job's files the person picks on the plan as tiles (selected, to move them where
|
|
21
|
+
they belong), and a selected tile is opened, or given a leader, from the bar. Measurements come into a drawing only
|
|
22
|
+
as the lengths of lines, from a line's length keypad in Draft (tools/dim.ts): a linked dimension is opened or let go
|
|
23
|
+
of from the bar, and one of the drawing's own driving dimensions is saved to the drawing's group as a measurement of
|
|
24
|
+
the job (Save to group), linked to it from then on.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Whether the + shows: Annotate's plan (not the wall view or 3D), for someone who can edit, with a host that links. */
|
|
28
|
+
export const linkPlusVisible = (): boolean => !ed.readOnly && ed.mode === 'annotate' && !elevOpen() && !is3d() && canLink();
|
|
29
|
+
|
|
30
|
+
export const LINK_PLUS_LABEL = 'Add a file';
|
|
31
|
+
|
|
32
|
+
/** The one selected tile, if a tile is all that's selected. */
|
|
33
|
+
export function selectedLink(): (Note & { link: LinkTarget }) | null {
|
|
34
|
+
if (ed.sel.length !== 1 || ed.sel[0].type !== 'note') return null;
|
|
35
|
+
const n = SK().notes.find(x => x.id === ed.sel[0].id);
|
|
36
|
+
return n && isLink(n) ? n : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Whether a tile's target can be opened: it's there (as far as the host has said), and the host can open it. */
|
|
40
|
+
export function canOpenLink(t: LinkTarget): boolean {
|
|
41
|
+
const info = linkInfoOf(t);
|
|
42
|
+
return info?.exists !== false && info?.canOpen !== false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The +: the person picks from the job, and what they picked goes on the plan (one undo step) in the middle of the
|
|
47
|
+
* screen, side by side and in rows if there are many — moved down and across, all together, from tiles already there
|
|
48
|
+
* — all selected, with Select markup on.
|
|
49
|
+
*/
|
|
50
|
+
export async function addLinks(): Promise<void> {
|
|
51
|
+
if (!linkPlusVisible()) return;
|
|
52
|
+
const pageId = store.curPageId;
|
|
53
|
+
const picked = await pickLinks();
|
|
54
|
+
if (!picked.length) return;
|
|
55
|
+
// (the person may have moved on meanwhile: another page, the other tab)
|
|
56
|
+
if (store.curPageId !== pageId || !linkPlusVisible()) { toast('Nothing was added: the drawing changed while you were picking. Try again.', { warn: true }); return; }
|
|
57
|
+
// (the page's tiles are all one size: the first is placed at a comfortable size on the screen, the rest follow it)
|
|
58
|
+
const T = mainT(), sk = SK(), ts = pageLinkSize(sk) ?? Math.round(clamp(LINK_TILE_PX / T.s, LINK_MIN_IN, LINK_MAX_IN) * 4) / 4;
|
|
59
|
+
// each a tile and a gap from the next, as many to a row as fit the screen
|
|
60
|
+
const cell = ts + LINK_GAP_PX / T.s, step = 30 / T.s;
|
|
61
|
+
const cols = Math.max(1, Math.min(picked.length, Math.floor((cvs.W - 160) / (cell * T.s))));
|
|
62
|
+
const rows = Math.ceil(picked.length / cols), [cx, cy] = s2w(cvs.W / 2, cvs.H / 2);
|
|
63
|
+
const spots = picked.map((_, i) => [cx + ((i % cols) - (cols - 1) / 2) * cell, cy - (Math.floor(i / cols) - (rows - 1) / 2) * cell]);
|
|
64
|
+
const taken = (px: number, py: number) => sk.notes.some(n => n.type === 'link' && Math.abs((n.x ?? 0) - px) < step / 2 && Math.abs((n.y ?? 0) - py) < step / 2);
|
|
65
|
+
let k = 0;
|
|
66
|
+
while (k < 40 && spots.some(([x, y]) => taken(x + k * step, y - k * step))) k++;
|
|
67
|
+
pushHistory();
|
|
68
|
+
const ids = picked.map((t, i) => {
|
|
69
|
+
const n: Note = { id: uid('lk'), type: 'link', x: spots[i][0] + k * step, y: spots[i][1] - k * step, ts, color: LINK_COLOR, link: linkSnapshot(t) };
|
|
70
|
+
sk.notes.push(n);
|
|
71
|
+
return n.id;
|
|
72
|
+
});
|
|
73
|
+
setTool('aselect');
|
|
74
|
+
ed.sel = ids.map(id => ({ type: 'note' as const, id }));
|
|
75
|
+
afterEdit();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The space between tiles added together, on the screen (points). */
|
|
79
|
+
const LINK_GAP_PX = 16;
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
Every tile on a page is one size, on the drawing (so it zooms with the plan and prints at that size): the Tile size
|
|
83
|
+
slider above the bar, shown while a tile is selected, sizes them all together.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/** The size of the page's tiles, inches on the drawing (null: it has none). */
|
|
87
|
+
export function pageLinkSize(sk = SK()): number | null {
|
|
88
|
+
const n = sk.notes.find(isLink);
|
|
89
|
+
return n ? linkSide(n) : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Whether the Tile size slider shows: Annotate's plan, for someone who can edit, with a tile among what's selected. */
|
|
93
|
+
export function linkSizeVisible(): boolean {
|
|
94
|
+
if (ed.readOnly || ed.mode !== 'annotate' || elevOpen() || is3d() || !ed.sel.length) return false;
|
|
95
|
+
const sk = SK();
|
|
96
|
+
return ed.sel.some(x => x.type === 'note' && sk.notes.find(n => n.id === x.id)?.type === 'link');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Size every tile on the page (inches, to the quarter, from LINK_MIN_IN to LINK_MAX_IN), drawn at once — for the slider
|
|
101
|
+
* as it moves. The caller makes the undo step before, and finishes the edit after.
|
|
102
|
+
*/
|
|
103
|
+
export function setLinkSize(inches: number): void {
|
|
104
|
+
const v = Math.round(clamp(inches, LINK_MIN_IN, LINK_MAX_IN) * 4) / 4;
|
|
105
|
+
for (const n of SK().notes) if (isLink(n)) n.ts = v;
|
|
106
|
+
requestDraw();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The slider runs along a log scale (a few inches to many feet, each step as noticeable as the last): 0 to SIZE_STEPS. */
|
|
110
|
+
export const SIZE_STEPS = 1000;
|
|
111
|
+
const LOG_MIN = Math.log(LINK_MIN_IN), LOG_SPAN = Math.log(LINK_MAX_IN) - LOG_MIN;
|
|
112
|
+
export const sizeToStep = (inches: number): number => Math.round(((Math.log(clamp(inches, LINK_MIN_IN, LINK_MAX_IN)) - LOG_MIN) / LOG_SPAN) * SIZE_STEPS);
|
|
113
|
+
export const stepToSize = (step: number): number => Math.exp(LOG_MIN + (clamp(step, 0, SIZE_STEPS) / SIZE_STEPS) * LOG_SPAN);
|
|
114
|
+
|
|
115
|
+
/** A tile's leader on or off. On, it points out beside the tile (to the right, or the left near the screen's edge). */
|
|
116
|
+
export function toggleLeader(id: string): void {
|
|
117
|
+
const n = SK().notes.find(x => x.id === id);
|
|
118
|
+
if (!n || !isLink(n)) return;
|
|
119
|
+
pushHistory();
|
|
120
|
+
if (n.leader) delete n.leader;
|
|
121
|
+
else {
|
|
122
|
+
const b = linkBox(mainT(), n), right = b.x + b.s + 80 < cvs.W - 24;
|
|
123
|
+
const [x, y] = s2w(right ? b.x + b.s + 70 : b.x - 70, b.cy + 50);
|
|
124
|
+
n.leader = [x, y];
|
|
125
|
+
}
|
|
126
|
+
afterEdit();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Open what the selected tile is for. */
|
|
130
|
+
export function openSelectedLink(): void {
|
|
131
|
+
const n = selectedLink();
|
|
132
|
+
if (!n) return;
|
|
133
|
+
if (!canOpenLink(n.link)) { toast(linkInfoOf(n.link)?.exists === false ? 'That’s been removed from the job, so it can’t be opened.' : 'That can’t be opened from here.', { warn: true }); return; }
|
|
134
|
+
openLink(n.link);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A view-only tap on the plan: on a link's tile, the person is asked, then what it's for opens. */
|
|
138
|
+
export function readOnlyTap(sx: number, sy: number, touch: boolean): void {
|
|
139
|
+
if (!ed.readOnly || !canLink()) return;
|
|
140
|
+
const n = linkAt(sx, sy, touch);
|
|
141
|
+
if (!n || !canOpenLink(n.link)) return;
|
|
142
|
+
const t = n.link, where = linkInfoOf(t)?.groupName ?? t.groupName;
|
|
143
|
+
askConfirm({
|
|
144
|
+
title: 'Open ' + linkName(t) + '?',
|
|
145
|
+
msg: where ? 'It’s in ' + where + '.' : undefined,
|
|
146
|
+
choices: [{ label: 'Open', run: () => openLink(t) }, { label: 'Cancel', run: () => {} }],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Let go of a dimension's link: its value stays, the drawing's alone now. */
|
|
151
|
+
export function unlinkDim(id: string): void {
|
|
152
|
+
const c = SK().cons[id];
|
|
153
|
+
if (!c?.measurementRef) return;
|
|
154
|
+
pushHistory();
|
|
155
|
+
delete c.measurementRef;
|
|
156
|
+
afterEdit();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Open the measurement a dimension is linked to. */
|
|
160
|
+
export function openDimLink(id: string): void {
|
|
161
|
+
const t = SK().cons[id]?.measurementRef;
|
|
162
|
+
if (!t) return;
|
|
163
|
+
if (!canOpenLink(t)) { toast(linkInfoOf(t)?.exists === false ? 'That measurement has been removed from the job, so it can’t be opened.' : 'That can’t be opened from here.', { warn: true }); return; }
|
|
164
|
+
openLink(t);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Whether a dimension can be saved to the drawing's group: one of the drawing's own driving dimensions with a value,
|
|
169
|
+
* on the plan, with a host that can save one there.
|
|
170
|
+
*/
|
|
171
|
+
export function canSaveDim(id: string): boolean {
|
|
172
|
+
const c = SK().cons[id], h = linksHost();
|
|
173
|
+
return !!c && !ed.readOnly && !elevOpen() && !!h?.saveMeasurement && h.canSave !== false && !c.measurementRef && !c.measurementId && !!dimMeasurement(c);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Save to group: the dimension becomes a measurement of the drawing's group (in its list, like any other — with the
|
|
178
|
+
* laser reading it's from, if one set it), and the dimension is linked to it from then on.
|
|
179
|
+
*/
|
|
180
|
+
export async function saveDimToGroup(id: string): Promise<void> {
|
|
181
|
+
const h = linksHost(), c = SK().cons[id];
|
|
182
|
+
if (!h || !c || !canSaveDim(id)) return;
|
|
183
|
+
const m = dimMeasurement(c)!, pageId = store.curPageId, was = JSON.stringify(c);
|
|
184
|
+
let t: LinkTarget | null = null;
|
|
185
|
+
try { t = await h.saveMeasurement!({ kind: m.kind, value: m.value, ...(c.capture !== undefined ? { capture: c.capture } : {}) }); } catch { t = null; }
|
|
186
|
+
if (!t) { toast('It couldn’t be saved to the group. Check the connection and try again.', { warn: true }); return; }
|
|
187
|
+
// (the drawing may have moved on meanwhile: the dimension's linked as it was saved, or not at all)
|
|
188
|
+
const cur = store.curPageId === pageId ? SK().cons[id] : undefined;
|
|
189
|
+
if (!cur || JSON.stringify(cur) !== was) { toast('Saved to the group, but the dimension changed meanwhile, so it isn’t linked. To link it, select its line, tap +, and pick it under the keys.', { warn: true }); return; }
|
|
190
|
+
pushHistory();
|
|
191
|
+
cur.measurementRef = linkSnapshot(t);
|
|
192
|
+
delete cur.capture;
|
|
193
|
+
afterEdit();
|
|
194
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { allCons } from '../model/faces';
|
|
2
|
+
import { linkInfoOf } from '../model/links';
|
|
3
|
+
import { dimMeasurement, dimValueOf } from '../model/measurement';
|
|
4
|
+
import { onSessionReset } from '../model/session';
|
|
5
|
+
import { SK, store } from '../model/store';
|
|
6
|
+
import { setDimValue } from '../tools/dim';
|
|
7
|
+
import { linksHost, onLinkInfo } from './links';
|
|
8
|
+
import { staleEcho } from './linkWrite';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Dimensions linked to measurements of the job follow them: when the job says a measurement is something else now
|
|
12
|
+
(changed elsewhere, on another device, by someone else), or the drawing has a different value for it (an undo past
|
|
13
|
+
a change both made), the dimension takes the job's value. On the page being edited the drawing solves to it, as a
|
|
14
|
+
step of no undo of its own; on other pages the value is set, and the drawing solves when that page is next edited.
|
|
15
|
+
One the drawing can't hold is left as it is (and not tried again until the job's value changes).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Values tried on dimensions and refused, by "page|constraint": not tried again until the job's value changes. */
|
|
19
|
+
const refused = new Map<string, number>();
|
|
20
|
+
let following = false;
|
|
21
|
+
onSessionReset(() => { refused.clear(); following = false; });
|
|
22
|
+
|
|
23
|
+
/** Bring every linked dimension in the project to what the job says it is now. */
|
|
24
|
+
export function followLinked(): void {
|
|
25
|
+
const p = store.project;
|
|
26
|
+
if (following || !p || !linksHost()) return;
|
|
27
|
+
following = true;
|
|
28
|
+
try {
|
|
29
|
+
for (const pg of p.pages) for (const [cid, c] of allCons(pg.sk)) {
|
|
30
|
+
const t = c.measurementRef, info = t && linkInfoOf(t);
|
|
31
|
+
if (!t || !info || info.exists === false || info.value == null || staleEcho(t, info.value)) continue;
|
|
32
|
+
const m = dimMeasurement(c), key = pg.id + '|' + cid;
|
|
33
|
+
if (!m || m.value === info.value) { refused.delete(key); continue; }
|
|
34
|
+
if (refused.get(key) === info.value) continue;
|
|
35
|
+
const v = dimValueOf(c, { value: info.value });
|
|
36
|
+
if (pg.id === store.curPageId && SK().cons[cid] === c) {
|
|
37
|
+
if (!setDimValue(cid, v, undefined, { quiet: true })) refused.set(key, info.value);
|
|
38
|
+
} else c.value = v;
|
|
39
|
+
}
|
|
40
|
+
} finally { following = false; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onLinkInfo(() => followLinked());
|