@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/export/pdf.ts
CHANGED
|
@@ -103,7 +103,7 @@ class Recorder implements Plan {
|
|
|
103
103
|
else if (p.t === 'arc') for (const q of arcPoints(p.c, p.r, p.s, p.e, 16)) add(q.x, q.y);
|
|
104
104
|
else if (p.t === 'text') add(p.p.x, p.p.y, p.h * Math.max(1, p.s.length * 0.3));
|
|
105
105
|
else if (p.t === 'fill') for (const q of p.pts) add(q.x, q.y);
|
|
106
|
-
else if (p.shape.kind === '
|
|
106
|
+
else if (p.shape.kind === 'polys') for (const q of p.shape.polys.flat()) add(q.x, q.y);
|
|
107
107
|
else add(p.shape.c.x, p.shape.c.y, p.shape.r + p.thick / 2);
|
|
108
108
|
}
|
|
109
109
|
return x0 <= x1 ? { x0, y0, x1, y1 } : null;
|
|
@@ -145,8 +145,12 @@ const STYLE: Record<string, { w: number; rgb: [number, number, number]; dash?: s
|
|
|
145
145
|
SPACES: { w: 0.4, rgb: [0.1, 0.1, 0.1] },
|
|
146
146
|
DIMENSIONS: { w: 0.3, rgb: [0.2, 0.2, 0.2] },
|
|
147
147
|
MARKUP: { w: 0.8, rgb: [0.85, 0.12, 0.1] },
|
|
148
|
+
// (the palette's blue, as a new link's leader is)
|
|
149
|
+
LINKS: { w: 0.6, rgb: [0.012, 0.392, 0.957] },
|
|
150
|
+
CABINETS: { w: 0.45, rgb: [0.35, 0.35, 0.35] },
|
|
151
|
+
CABINETS_UPPER: { w: 0.45, rgb: [0.35, 0.35, 0.35], dash: '[4 2.5] 0' },
|
|
148
152
|
};
|
|
149
|
-
const FILL: Record<string, [number, number, number]> = { SPACES: [0.94, 0.95, 0.97], KNOCKOUT: [1, 1, 1] };
|
|
153
|
+
const FILL: Record<string, [number, number, number]> = { SPACES: [0.94, 0.95, 0.97], KNOCKOUT: [1, 1, 1], CABINETS: [0.88, 0.88, 0.88] };
|
|
150
154
|
const WALL_RGB: [number, number, number] = [0.22, 0.22, 0.24];
|
|
151
155
|
const rgb = (c: [number, number, number]) => c.map(f).join(' ');
|
|
152
156
|
|
|
@@ -189,11 +193,15 @@ export function sketchToPdf(sk: Sketch, opts: PdfOptions = {}): string {
|
|
|
189
193
|
const q = p.pts.map(T);
|
|
190
194
|
c.push(rgb(FILL[p.layer] || FILL.SPACES) + ' rg ' + q.map((v, i) => f(v.x) + ' ' + f(v.y) + (i ? ' l' : ' m')).join(' ') + ' h f');
|
|
191
195
|
} else if (p.t === 'wall') {
|
|
192
|
-
// a
|
|
193
|
-
c.push('q 0 J ' + f(p.thick * s) + ' w ' + rgb(WALL_RGB) + ' RG []0 d');
|
|
196
|
+
// a straight wall: its outline, filled (cut to fit its joints); a curve: a fat stroke of the wall's thickness
|
|
194
197
|
const sh = p.shape;
|
|
195
|
-
if (sh.kind === '
|
|
196
|
-
|
|
198
|
+
if (sh.kind === 'polys') {
|
|
199
|
+
const path = sh.polys.filter(q => q.length >= 3).map(q => q.map(T).map((v, i) => f(v.x) + ' ' + f(v.y) + (i ? ' l' : ' m')).join(' ') + ' h').join(' ');
|
|
200
|
+
if (path) c.push(rgb(WALL_RGB) + ' rg ' + path + ' f');
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
c.push('q 0 J ' + f(p.thick * s) + ' w ' + rgb(WALL_RGB) + ' RG []0 d');
|
|
204
|
+
if (sh.kind === 'arc') c.push(arcPath(T(sh.c), sh.r * s, sh.startDeg, sh.endDeg, true) + 'S');
|
|
197
205
|
else c.push(arcPath(T(sh.c), sh.r * s, 0, 360, true) + 'h S');
|
|
198
206
|
c.push('Q');
|
|
199
207
|
stroke = '';
|
|
@@ -210,7 +218,7 @@ export function sketchToPdf(sk: Sketch, opts: PdfOptions = {}): string {
|
|
|
210
218
|
} else {
|
|
211
219
|
const size = Math.min(14, Math.max(5, p.h * s));
|
|
212
220
|
const room = p.layer === 'SPACES', bold = room && p.h >= 6;
|
|
213
|
-
text(T(p.p), size, p.s, { bold, rot: p.rot, rgb: p.layer === 'MARKUP' ? STYLE.
|
|
221
|
+
text(T(p.p), size, p.s, { bold, rot: p.rot, rgb: p.layer === 'MARKUP' || p.layer === 'LINKS' ? STYLE[p.layer].rgb : p.layer === 'DIMENSIONS' ? [0.15, 0.15, 0.15] : undefined });
|
|
214
222
|
}
|
|
215
223
|
}
|
|
216
224
|
if (!bb) text({ x: ox, y: oy }, 12, 'Nothing drawn on this page yet', { rgb: [0.5, 0.5, 0.5] });
|
package/src/export/plan.ts
CHANGED
|
@@ -2,19 +2,28 @@ import { isDim, measure } from '../model/constraints';
|
|
|
2
2
|
import { hyp, norm2, pointInPoly } from '../model/geom';
|
|
3
3
|
import { edgeS, measureOpeningDim, openingSpan, refPoint, refS } from '../model/openings';
|
|
4
4
|
import { roomGeom } from '../model/rooms';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { smoothPolyline } from '../model/smooth';
|
|
6
|
+
import { roomValid } from '../model/sketch';
|
|
7
|
+
import { dimEnds, dimRadius } from '../model/planGeom';
|
|
8
|
+
import { wallPieces } from '../model/wallshape';
|
|
9
|
+
import { cabinetFootprints } from '../model/facesolve';
|
|
10
|
+
import { isSegment, noteLook, noteTextIn, shapeOutline } from '../model/markup';
|
|
11
|
+
import type { LinkTarget, Note, Sketch, XY } from '../model/types';
|
|
12
|
+
import { isLink, linkInfoOf, linkName, linkSide } from '../model/links';
|
|
7
13
|
import { fmtAng, fmtArea, fmtLen } from '../units';
|
|
8
14
|
|
|
9
15
|
/*
|
|
10
16
|
What a page looks like on paper, independent of file format: the exporters (DXF, PDF) implement `Plan`
|
|
11
17
|
and `emitDrawing` walks the sketch once, in drawing order (room shading, walls, openings, labels,
|
|
12
|
-
dimensions, markup). World units are inches, y up.
|
|
18
|
+
dimensions, markup — links' tiles on a LINKS layer of their own). World units are inches, y up.
|
|
13
19
|
*/
|
|
14
20
|
|
|
15
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* A solid wall to draw: the straight walls' outlines (cut to fit their joints, see model/wallshape.ts — one shape, so
|
|
23
|
+
* no seams show where they meet), or a curve.
|
|
24
|
+
*/
|
|
16
25
|
export type WallShape =
|
|
17
|
-
| { kind: '
|
|
26
|
+
| { kind: 'polys'; polys: XY[][] }
|
|
18
27
|
| { kind: 'arc'; c: XY; r: number; startDeg: number; endDeg: number }
|
|
19
28
|
| { kind: 'circle'; c: XY; r: number };
|
|
20
29
|
|
|
@@ -34,8 +43,70 @@ export interface Plan {
|
|
|
34
43
|
export interface DrawOptions {
|
|
35
44
|
/** include dimensions (driving, reference, print, door/window) — default true */
|
|
36
45
|
dims?: boolean;
|
|
37
|
-
/** include markup (pen, arrows, clouds, notes) — default true */
|
|
46
|
+
/** include markup (pen, lines, arrows, shapes, clouds, notes) — default true */
|
|
38
47
|
markup?: boolean;
|
|
48
|
+
/** include cabinets' footprints (from the walls' views) — default true */
|
|
49
|
+
cabinets?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* What a link's tile says on paper: its name, and a measurement's value (default: what the host last said, or what
|
|
52
|
+
* it was when it was linked).
|
|
53
|
+
*/
|
|
54
|
+
linkLabel?: (t: LinkTarget) => { name: string; value?: string };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What a link's tile says on paper, unless the exporter's told otherwise. */
|
|
58
|
+
const defaultLinkLabel = (t: LinkTarget): { name: string; value?: string } => {
|
|
59
|
+
const info = linkInfoOf(t);
|
|
60
|
+
return { name: (info?.exists === false ? 'Removed: ' : '') + linkName(t), value: t.kind === 'measurement' ? info?.text ?? t.text : undefined };
|
|
61
|
+
};
|
|
62
|
+
const KIND_WORD: Record<string, string> = { photo: 'Photo', pdf: 'PDF', note: 'Note', calculator: 'Calculator', label: 'Label', drawing: 'Drawing' };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A name on paper, where nothing can be measured (a DXF has no fonts): about `perLine` characters a line, broken at a
|
|
66
|
+
* space where it can be, on two lines at most — the second cut short if even that's too little.
|
|
67
|
+
*/
|
|
68
|
+
export function wrapName(text: string, perLine: number): string[] {
|
|
69
|
+
if (text.length <= perLine) return [text];
|
|
70
|
+
const sp = text.lastIndexOf(' ', perLine), cut = sp > 0 ? sp : perLine, next = sp > 0 ? sp + 1 : perLine;
|
|
71
|
+
const first = text.slice(0, cut).trimEnd();
|
|
72
|
+
let rest = text.slice(next).trim();
|
|
73
|
+
if (rest.length > perLine) rest = rest.slice(0, Math.max(1, perLine - 1)).trimEnd() + '…';
|
|
74
|
+
return rest ? [first, rest] : [first];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** How many characters of a tile's name go across its box (84 of its 96, at text 11 high: about 0.55 of that a character). */
|
|
78
|
+
const NAME_PER_LINE = Math.floor(84 / (0.55 * 11));
|
|
79
|
+
|
|
80
|
+
/** A link's tile on the LINKS layer: its box, what it says, and its leader to what it points at (with its arrowheads). */
|
|
81
|
+
function emitLink(d: Plan, n: Note & { link: LinkTarget }, label: NonNullable<DrawOptions['linkLabel']>): void {
|
|
82
|
+
const L = 'LINKS', x = n.x ?? 0, y = n.y ?? 0, side = linkSide(n), h = side / 2;
|
|
83
|
+
const c = [{ x: x - h, y: y - h }, { x: x + h, y: y - h }, { x: x + h, y: y + h }, { x: x - h, y: y + h }];
|
|
84
|
+
for (let i = 0; i < 4; i++) d.line(L, c[i], c[(i + 1) % 4]);
|
|
85
|
+
// laid out as the card is on the screen (96 across): a measurement's value in the middle and its name below; a
|
|
86
|
+
// file's kind where its picture is, and its name below
|
|
87
|
+
const u = side / 96, { name, value } = label(n.link);
|
|
88
|
+
if (n.link.kind === 'measurement') {
|
|
89
|
+
if (value) d.text(L, { x, y: y + h - 46 * u }, 18 * u, value);
|
|
90
|
+
d.text(L, { x, y: y + h - 75 * u }, 11 * u, wrapName(name, NAME_PER_LINE).length > 1 ? name.slice(0, NAME_PER_LINE - 1).trimEnd() + '…' : name);
|
|
91
|
+
} else {
|
|
92
|
+
const kind = KIND_WORD[linkInfoOf(n.link)?.fileType ?? n.link.fileType ?? ''] ?? 'File';
|
|
93
|
+
d.text(L, { x, y: y + h - 37 * u }, 11 * u, kind);
|
|
94
|
+
// its name across its box (on paper it can't zoom in to show more): two lines, centred in the strip under the picture
|
|
95
|
+
const lines = wrapName(name, NAME_PER_LINE);
|
|
96
|
+
if (lines.length === 1) d.text(L, { x, y: y + h - 81 * u }, 11 * u, lines[0]);
|
|
97
|
+
else lines.forEach((l, i) => d.text(L, { x, y: y + h - (82 + (i ? 1 : -1) * 6.875) * u }, 11 * u, l));
|
|
98
|
+
}
|
|
99
|
+
if (!n.leader) return;
|
|
100
|
+
const [tx, ty] = n.leader, dx = tx - x, dy = ty - y, m = Math.max(Math.abs(dx), Math.abs(dy));
|
|
101
|
+
if (m <= h) return;
|
|
102
|
+
const s = { x: x + (dx * h) / m, y: y + (dy * h) / m }, tip = { x: tx, y: ty }, look = noteLook(n);
|
|
103
|
+
d.line(L, s, tip);
|
|
104
|
+
const head = (from: XY, to: XY) => {
|
|
105
|
+
const a = Math.atan2(to.y - from.y, to.x - from.x), hl = 5;
|
|
106
|
+
for (const k of [-0.45, 0.45]) d.line(L, to, { x: to.x - hl * Math.cos(a + k), y: to.y - hl * Math.sin(a + k) });
|
|
107
|
+
};
|
|
108
|
+
if (look.endCap === 'arrow') head(s, tip);
|
|
109
|
+
if (look.startCap === 'arrow') head(tip, s);
|
|
39
110
|
}
|
|
40
111
|
|
|
41
112
|
/** Text height for dimension values and labels, inches. */
|
|
@@ -90,20 +161,20 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
|
|
|
90
161
|
// room shading first, so walls and labels sit on top
|
|
91
162
|
if (d.fill) for (const id in sk.rooms) { const r = sk.rooms[id]; if (roomValid(sk, r)) d.fill('SPACES', roomGeom(sk, r).inner); }
|
|
92
163
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
164
|
+
// cabinets' footprints, under the walls: base cabinets filled on CABINETS, wall (upper) cabinets on CABINETS_UPPER (dashed)
|
|
165
|
+
if (opts.cabinets !== false) for (const fp of cabinetFootprints(sk)) {
|
|
166
|
+
const layer = fp.upper ? 'CABINETS_UPPER' : 'CABINETS';
|
|
167
|
+
if (!fp.upper && d.fill) d.fill('CABINETS', fp.pts);
|
|
168
|
+
fp.pts.forEach((p, i) => d.line(layer, p, fp.pts[(i + 1) % fp.pts.length]));
|
|
169
|
+
}
|
|
98
170
|
|
|
99
|
-
// geometry
|
|
171
|
+
// geometry: straight walls as their outlines, cut to fit where they meet (as on screen)
|
|
172
|
+
const pieces = d.wall ? wallPieces(sk, w => !w.constr) : [];
|
|
173
|
+
if (pieces.length) d.wall!({ kind: 'polys', polys: pieces.map(p => p.pts) }, 0);
|
|
100
174
|
for (const id in sk.lines) {
|
|
101
175
|
const l = sk.lines[id], A = P(l.a), B = P(l.b);
|
|
102
|
-
if (l.kind === 'wall' && !l.constr && d.wall)
|
|
103
|
-
|
|
104
|
-
const ea = deg2[l.a] > 1 ? e : 0, eb = deg2[l.b] > 1 ? e : 0;
|
|
105
|
-
d.wall({ kind: 'line', a: { x: A.x - ux * ea, y: A.y - uy * ea }, b: { x: B.x + ux * eb, y: B.y + uy * eb } }, l.thick);
|
|
106
|
-
} else d.line(l.constr ? 'CONSTRUCTION' : l.kind === 'wall' ? 'WALLS' : 'LINES', A, B);
|
|
176
|
+
if (l.kind === 'wall' && !l.constr && d.wall) continue;
|
|
177
|
+
d.line(l.constr ? 'CONSTRUCTION' : l.kind === 'wall' ? 'WALLS' : 'LINES', A, B);
|
|
107
178
|
}
|
|
108
179
|
for (const id in sk.circles) {
|
|
109
180
|
const c = sk.circles[id];
|
|
@@ -159,26 +230,31 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
|
|
|
159
230
|
const v = c.driving === false ? measure(sk, c) : c.value!;
|
|
160
231
|
const wrap = (s: string) => (ref ? '(' + s + ')' : s);
|
|
161
232
|
if (c.type === 'dist') {
|
|
162
|
-
|
|
163
|
-
|
|
233
|
+
// (on a wall's face: its corners)
|
|
234
|
+
const e = dimEnds(sk, c);
|
|
235
|
+
if (!e) continue;
|
|
236
|
+
const { a, b } = e;
|
|
237
|
+
if (hyp(b.x - a.x, b.y - a.y) < 1e-6) continue;
|
|
164
238
|
const n = norm2(-(b.y - a.y), b.x - a.x);
|
|
165
239
|
// a label sitting on its line in the app goes out to the side: overall wall lengths sit outside the
|
|
166
240
|
// door/window string (architectural order: openings close to the wall, overall dimensions further out)
|
|
167
241
|
const off = c.off || (c.line ? WALL_DIM_OFF * outsideSide(sk, c.line) : 0);
|
|
168
242
|
linearDim(d, a, b, n, off, wrap(fmtLen(v)));
|
|
169
243
|
} else if (c.type === 'pldist') {
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
const n = norm2(-(
|
|
174
|
-
linearDim(d,
|
|
244
|
+
// (between walls' faces: face to face)
|
|
245
|
+
const e = dimEnds(sk, c);
|
|
246
|
+
if (!e) continue;
|
|
247
|
+
const n = norm2(-(e.b.y - e.a.y), e.b.x - e.a.x);
|
|
248
|
+
linearDim(d, e.a, e.b, n, 0, wrap(fmtLen(v)));
|
|
175
249
|
} else if (c.type === 'diam') {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
250
|
+
// (across a curved wall's face: on that face)
|
|
251
|
+
const g = dimRadius(sk, c);
|
|
252
|
+
if (!g) continue;
|
|
253
|
+
const { o, r } = g;
|
|
179
254
|
const a = c.ang ?? Math.PI / 3, u = [Math.cos(a), Math.sin(a)];
|
|
180
|
-
|
|
181
|
-
d.
|
|
255
|
+
// a radius (a fillet's) from the centre out through the arc; a diameter right across
|
|
256
|
+
d.line('DIMENSIONS', c.rad ? o : { x: o.x - u[0] * r, y: o.y - u[1] * r }, { x: o.x + u[0] * (r + 12), y: o.y + u[1] * (r + 12) });
|
|
257
|
+
d.text('DIMENSIONS', { x: o.x + u[0] * (r + 12) + (u[0] >= 0 ? 14 : -14), y: o.y + u[1] * (r + 12) }, TEXT_H, c.rad ? 'R ' + wrap(fmtLen(v / 2)) : 'Ø ' + wrap(fmtLen(v)));
|
|
182
258
|
} else if (c.type === 'angle') {
|
|
183
259
|
const l1 = sk.lines[c.l1!], l2 = sk.lines[c.l2!];
|
|
184
260
|
const sh = [l1.a, l1.b].find(p => p === l2.a || p === l2.b);
|
|
@@ -208,19 +284,27 @@ export function emitDrawing(sk: Sketch, d: Plan, opts: DrawOptions = {}): void {
|
|
|
208
284
|
if (opts.markup !== false) {
|
|
209
285
|
for (const nt of sk.notes) {
|
|
210
286
|
const L = 'MARKUP';
|
|
211
|
-
if ((nt
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
287
|
+
if (isLink(nt)) { emitLink(d, nt, opts.linkLabel ?? defaultLinkLabel); continue; }
|
|
288
|
+
const outline = shapeOutline(nt);
|
|
289
|
+
if (outline) {
|
|
290
|
+
for (let i = 0; i < outline.length; i++) { const a = outline[i], b = outline[(i + 1) % outline.length]; d.line(L, { x: a[0], y: a[1] }, { x: b[0], y: b[1] }); }
|
|
291
|
+
} else if ((nt.type === 'pen' || isSegment(nt)) && nt.pts) {
|
|
292
|
+
// (a pen stroke's curve, as drawn, in pieces within a hundredth of an inch of it)
|
|
293
|
+
const run = smoothPolyline(nt.pts, 0.01);
|
|
294
|
+
for (let i = 1; i < run.length; i++) d.line(L, { x: run[i - 1][0], y: run[i - 1][1] }, { x: run[i][0], y: run[i][1] });
|
|
295
|
+
const look = noteLook(nt), n = nt.pts.length;
|
|
296
|
+
const head = (p: [number, number], q: [number, number]) => {
|
|
215
297
|
const a = Math.atan2(q[1] - p[1], q[0] - p[0]), h = 5;
|
|
216
298
|
for (const s of [-0.45, 0.45]) d.line(L, { x: q[0], y: q[1] }, { x: q[0] - h * Math.cos(a + s), y: q[1] - h * Math.sin(a + s) });
|
|
217
|
-
}
|
|
299
|
+
};
|
|
300
|
+
if (n > 1 && look.endCap === 'arrow') head(nt.pts[n - 2], nt.pts[n - 1]);
|
|
301
|
+
if (n > 1 && look.startCap === 'arrow') head(nt.pts[1], nt.pts[0]);
|
|
218
302
|
} else if (nt.type === 'cloud' && nt.pts) {
|
|
219
303
|
const [[x0, y0], [x1, y1]] = nt.pts;
|
|
220
304
|
const c = [{ x: x0, y: y0 }, { x: x1, y: y0 }, { x: x1, y: y1 }, { x: x0, y: y1 }];
|
|
221
305
|
for (let i = 0; i < 4; i++) d.line(L, c[i], c[(i + 1) % 4]);
|
|
222
306
|
} else if (nt.type === 'text' && nt.text) {
|
|
223
|
-
d.text(L, { x: nt.x!, y: nt.y! },
|
|
307
|
+
d.text(L, { x: nt.x!, y: nt.y! }, noteTextIn(nt), nt.text);
|
|
224
308
|
}
|
|
225
309
|
}
|
|
226
310
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,4 +10,9 @@ export type { CadDataProvider, CadFileMeta, CadMeasurements, CadMeasurementValue
|
|
|
10
10
|
export { memoryProvider } from './data/memory';
|
|
11
11
|
export { setFileSaver } from './react/ports';
|
|
12
12
|
export { laserReading, setLaserConnected } from './react/host';
|
|
13
|
+
export type { HostKeypad, HostKeypadExtra, HostKeypadRequest, MeasurementList, MeasurementListPage } from './react/hostKeypad';
|
|
14
|
+
export type { CadLinkPick, CadLinks, CadSaveMeasurement, MeasurementKind } from './editor/links';
|
|
15
|
+
export type { LinkInfo, LinkTarget } from './model/types';
|
|
16
|
+
export { linkKey } from './model/links';
|
|
17
|
+
export { hasUnsavedChanges as cadHasUnsavedChanges, saveDrawing as saveCadDrawing, type CadSaveState } from './react/saving';
|
|
13
18
|
export type { SkiaFonts } from './skia/SkiaDrawCtx';
|
package/src/input/keyboard.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { elevKeyDown, elevOpen } from '../editor/elevation';
|
|
2
|
-
import { keypadOpen } from '../editor/keypad';
|
|
3
1
|
import { keyDown, keyUp, type KeyIn } from '../editor/keys';
|
|
4
2
|
|
|
5
|
-
/* DOM key events, handed to editor/keys.ts
|
|
3
|
+
/* DOM key events, handed to editor/keys.ts. */
|
|
6
4
|
|
|
7
5
|
const keyIn = (e: KeyboardEvent): KeyIn => ({ key: e.key, shift: e.shiftKey, mod: e.metaKey || e.ctrlKey, alt: e.altKey });
|
|
8
6
|
|
|
@@ -11,7 +9,7 @@ export function initKeyboard(): void {
|
|
|
11
9
|
const tag = (e.target && (e.target as HTMLElement).tagName) || '';
|
|
12
10
|
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
|
|
13
11
|
const k = keyIn(e);
|
|
14
|
-
if (
|
|
12
|
+
if (keyDown(k)) e.preventDefault();
|
|
15
13
|
});
|
|
16
14
|
document.addEventListener('keyup', e => keyUp(keyIn(e)));
|
|
17
15
|
}
|
package/src/model/constraints.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fmtAng, fmtLen } from '../units';
|
|
2
2
|
import { hyp } from './geom';
|
|
3
3
|
import { curveInfo } from './sketch';
|
|
4
|
+
import { faceLength } from './wallfaces';
|
|
4
5
|
import type { Analysis, Constraint, ConsType, Line, Sketch } from './types';
|
|
5
6
|
|
|
6
7
|
export const isDim = (c: Constraint): boolean => ['dist', 'pldist', 'diam', 'angle'].includes(c.type);
|
|
@@ -13,7 +14,7 @@ export const CONS_LABEL: Record<ConsType, string> = {
|
|
|
13
14
|
|
|
14
15
|
export function consLabel(c: Constraint): string {
|
|
15
16
|
if (c.type === 'dist' || c.type === 'pldist') return (c.type === 'dist' ? (c.line ? 'Length ' : 'Distance ') : 'Offset ') + fmtLen(c.value!);
|
|
16
|
-
if (c.type === 'diam') return 'Diameter ' + fmtLen(c.value!);
|
|
17
|
+
if (c.type === 'diam') return c.rad ? 'Radius ' + fmtLen(c.value! / 2) : 'Diameter ' + fmtLen(c.value!);
|
|
17
18
|
if (c.type === 'angle') return 'Angle ' + fmtAng(c.value!);
|
|
18
19
|
return CONS_LABEL[c.type] || c.type;
|
|
19
20
|
}
|
|
@@ -26,18 +27,26 @@ export function measure(sk: Sketch, c: Constraint): number {
|
|
|
26
27
|
const a = P(c.a!), b = P(c.b!);
|
|
27
28
|
if (c.kind === 'h') return Math.abs(b.x - a.x);
|
|
28
29
|
if (c.kind === 'v') return Math.abs(b.y - a.y);
|
|
30
|
+
// along a wall's face, corner to corner
|
|
31
|
+
if (c.face && c.line && sk.lines[c.line]?.kind === 'wall') return faceLength(sk, c.line, c.face);
|
|
29
32
|
return hyp(b.x - a.x, b.y - a.y);
|
|
30
33
|
}
|
|
31
34
|
case 'pldist': {
|
|
32
35
|
const l = sk.lines[c.l!], p = P(c.p!), a = P(l.a), b = P(l.b);
|
|
33
|
-
|
|
36
|
+
const d = Math.abs(((b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)) / (hyp(b.x - a.x, b.y - a.y) || 1e-12));
|
|
37
|
+
if (!c.faces) return d;
|
|
38
|
+
// between faces: less half of each wall's thickness for a face toward the other, more for a far face
|
|
39
|
+
const th = (id?: string) => { const w = id ? sk.lines[id] : undefined; return w && w.kind === 'wall' ? w.thick : 0; };
|
|
40
|
+
return d - (c.faces[0] * th(c.l) + c.faces[1] * th(c.l2)) / 2;
|
|
34
41
|
}
|
|
35
42
|
case 'diam': {
|
|
36
43
|
const ci = curveInfo(sk, c.c);
|
|
37
44
|
if (!ci) return 0;
|
|
38
|
-
|
|
45
|
+
// (across a curved wall's face: its centreline's, plus its thickness for the outer face, less for the inner)
|
|
46
|
+
const off = c.face && ci.o.kind === 'wall' ? c.face * (ci.o.thick || 4.5) : 0;
|
|
47
|
+
if (ci.kind === 'circle') return 2 * ci.o.r + off;
|
|
39
48
|
const o = P(ci.o.c), s = P(ci.o.a);
|
|
40
|
-
return 2 * hyp(s.x - o.x, s.y - o.y);
|
|
49
|
+
return 2 * hyp(s.x - o.x, s.y - o.y) + off;
|
|
41
50
|
}
|
|
42
51
|
case 'angle': {
|
|
43
52
|
const l1 = sk.lines[c.l1!], l2 = sk.lines[c.l2!];
|
package/src/model/demo.ts
CHANGED
|
@@ -15,8 +15,8 @@ export function demoSketch(): Sketch {
|
|
|
15
15
|
C({ type: 'h', a: p4, b: p5 }); C({ type: 'v', a: p5, b: p6 }); C({ type: 'h', a: p6, b: p1 });
|
|
16
16
|
C({ type: 'dist', a: p1, b: p2, kind: 'al', value: 240, line: l1, driving: true, off: 0 });
|
|
17
17
|
C({ type: 'dist', a: p6, b: p1, kind: 'al', value: 168, line: l6, driving: true, off: 0 });
|
|
18
|
-
sk.openings[uid('o')] = { line: l6, at: 42, w: 36, type: 'door', flip: true };
|
|
19
|
-
sk.openings[uid('o')] = { line: l1, at: 150, w: 48, type: 'window', flip: false };
|
|
18
|
+
sk.openings[uid('o')] = { line: l6, at: 42, w: 36, wSet: false, type: 'door', flip: true };
|
|
19
|
+
sk.openings[uid('o')] = { line: l1, at: 150, w: 48, wSet: true, type: 'window', flip: false };
|
|
20
20
|
sk.rooms[uid('r')] = { loop: [p1, p6, p5, p4, p3, p2], name: 'Living room' };
|
|
21
21
|
sk.seq.room = 2;
|
|
22
22
|
sk.notes.push({ id: uid('n'), type: 'arrow', pts: [[214, 196], [128, 158]], color: '#F070EE' });
|
|
@@ -25,7 +25,7 @@ export function demoSketch(): Sketch {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function newPage(name: string, sk?: Sketch): Page {
|
|
28
|
-
return { id: uid('pg'), name, sk: sk || emptySketch(),
|
|
28
|
+
return { id: uid('pg'), name, sk: sk || emptySketch(), underlays: [], view: null };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function seedProject(): Project {
|
package/src/model/elevation.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { edgeS, openingSpan } from './openings';
|
|
|
3
3
|
import { roomGeom } from './rooms';
|
|
4
4
|
import { settings } from './settings';
|
|
5
5
|
import { roomValid } from './sketch';
|
|
6
|
-
import
|
|
6
|
+
import { faceKey } from './faces';
|
|
7
|
+
import type { Line, Opening, OpeningEdge, Sketch, XY } from './types';
|
|
7
8
|
import { wallFace } from './wallfaces';
|
|
8
9
|
|
|
9
10
|
/*
|
|
@@ -106,19 +107,8 @@ export function elevOpenings(sk: Sketch, f: ElevFrame): ElevOpening[] {
|
|
|
106
107
|
return out.sort((p, q) => p.u0 - q.u0);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
/** Key of a wall face's
|
|
110
|
-
export const elevKey =
|
|
110
|
+
/** Key of a wall face's drawing in `Sketch.faces`. */
|
|
111
|
+
export const elevKey = faceKey;
|
|
111
112
|
|
|
112
|
-
/**
|
|
113
|
-
export const
|
|
114
|
-
|
|
115
|
-
/** Add a line to the face this frame looks at; a and b in view coordinates [u, z]. Returns its id. */
|
|
116
|
-
export function addElevLine(sk: Sketch, f: ElevFrame, a: [number, number], b: [number, number], guide: boolean, id: string): string {
|
|
117
|
-
const k = elevKey(f.lid, f.side);
|
|
118
|
-
sk.elev = sk.elev || {};
|
|
119
|
-
(sk.elev[k] = sk.elev[k] || []).push({ id, a: [toU(f, a[0]), a[1]], b: [toU(f, b[0]), b[1]], ...(guide ? { guide: true } : {}) });
|
|
120
|
-
return id;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** A drawn line's ends in view coordinates [u, z]. */
|
|
124
|
-
export const elevLineUZ = (f: ElevFrame, l: ElevLine): [[number, number], [number, number]] => [[toU(f, l.a[0]), l.a[1]], [toU(f, l.b[0]), l.b[1]]];
|
|
113
|
+
/** A face point (x = s, y = z) in view coordinates [u, z]. */
|
|
114
|
+
export const faceUZ = (f: ElevFrame, p: XY): [number, number] => [toU(f, p.x), p.y];
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { onSessionReset } from './session';
|
|
2
|
+
import type { Constraint, Sketch, WallFace } from './types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
What's drawn on wall faces (Sketch.faces): the parts that don't need the wall's geometry — keys, the plan's
|
|
6
|
+
anchors by name, and following a wall that's split. Keeping a face in step with the plan (its anchors, solving
|
|
7
|
+
it) is in model/facesolve.ts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Key of a wall face in `Sketch.faces`: the wall line and the side it's seen from (1: left of A → B, -1: right). */
|
|
11
|
+
export const faceKey = (lid: string, side: number): string => lid + '|' + (side > 0 ? 1 : -1);
|
|
12
|
+
export const faceKeyParts = (key: string): { lid: string; side: 1 | -1 } => {
|
|
13
|
+
const i = key.lastIndexOf('|');
|
|
14
|
+
return { lid: key.slice(0, i), side: key.slice(i + 1) === '-1' ? -1 : 1 };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** The wall face being edited (its key), or null while the plan is: SK() then gives that face's sketch. */
|
|
18
|
+
export const faceEdit = { key: null as string | null };
|
|
19
|
+
onSessionReset(() => { faceEdit.key = null; });
|
|
20
|
+
|
|
21
|
+
/** Whether a face has nothing on it but the plan's anchors (and isn't open): it isn't kept. */
|
|
22
|
+
export const faceEmpty = (face: Sketch): boolean =>
|
|
23
|
+
Object.values(face.points).every(p => p.anchor) && Object.values(face.cons).every(c => c.anchor) && !Object.keys(face.circles).length;
|
|
24
|
+
|
|
25
|
+
export const emptyFace = (): WallFace => ({ points: {}, lines: {}, circles: {}, arcs: {}, cons: {}, rooms: {}, openings: {}, notes: [], seq: { room: 1 }, cabs: {} });
|
|
26
|
+
|
|
27
|
+
/** The face drawing for `key`; made (empty) when `create` and there's none yet. */
|
|
28
|
+
export function faceOf(sk: Sketch, key: string, create: true): WallFace;
|
|
29
|
+
export function faceOf(sk: Sketch, key: string, create?: boolean): WallFace | null;
|
|
30
|
+
export function faceOf(sk: Sketch, key: string, create = false): WallFace | null {
|
|
31
|
+
const f = sk.faces?.[key];
|
|
32
|
+
if (f) return f;
|
|
33
|
+
if (!create) return null;
|
|
34
|
+
sk.faces = sk.faces || {};
|
|
35
|
+
return (sk.faces[key] = emptyFace());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The plan's anchors on a face, by name. Points: "@a0" / "@aH" the corner at the wall's start end (A), at the floor
|
|
40
|
+
* and at the ceiling, "@b0" / "@bH" the one at its far end; "@o:<opening>:<edge>:0" / ":1" a door's or window's
|
|
41
|
+
* corner (its s0 or s1 edge, at its sill or head). Edges: "@floor", "@ceil", "@ea" / "@eb" (the wall's ends),
|
|
42
|
+
* "@o:<opening>:sill" / ":head" / ":s0" / ":s1".
|
|
43
|
+
*/
|
|
44
|
+
export const cornerAnchor = (end: 'a' | 'b', top = false): string => '@' + end + (top ? 'H' : '0');
|
|
45
|
+
export const openingAnchor = (op: string, edge: 's0' | 's1', top = false): string => '@o:' + op + ':' + edge + ':' + (top ? 1 : 0);
|
|
46
|
+
export const FLOOR = '@floor', CEILING = '@ceil';
|
|
47
|
+
export const endEdge = (end: 'a' | 'b'): string => '@e' + end;
|
|
48
|
+
export const openingEdge = (op: string, side: 'sill' | 'head' | 's0' | 's1'): string => '@o:' + op + ':' + side;
|
|
49
|
+
|
|
50
|
+
/** Whether a point or edge of the face is one of the plan's anchors. */
|
|
51
|
+
export const isAnchorPt = (face: Sketch, id: string): boolean => !!face.points[id]?.anchor;
|
|
52
|
+
export const isAnchorLine = (face: Sketch, id: string): boolean => !!face.lines[id]?.anchor;
|
|
53
|
+
|
|
54
|
+
/** Every constraint in the sketch — the plan's and each wall face's — with its id. */
|
|
55
|
+
export function allCons(sk: Sketch): [string, Constraint][] {
|
|
56
|
+
const out = Object.entries(sk.cons);
|
|
57
|
+
if (sk.faces) for (const key in sk.faces) out.push(...Object.entries(sk.faces[key].cons));
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The wall face a constraint is on (null for the plan's own). */
|
|
62
|
+
export function faceOfCons(sk: Sketch, cid: string): { key: string; face: WallFace } | null {
|
|
63
|
+
if (sk.faces) for (const key in sk.faces) if (sk.faces[key].cons[cid]) return { key, face: sk.faces[key] };
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A cabinet's corners. */
|
|
68
|
+
export const cabPoints = (cb: { bl: string; br: string; tr: string; tl: string }): string[] => [cb.bl, cb.br, cb.tr, cb.tl];
|
|
69
|
+
|
|
70
|
+
/** The door or window whose corner (a wall face's anchor) `pid` is, if it is one. */
|
|
71
|
+
export const openingOfPoint = (pid: string): string | null => /^@o:([^:]+):s[01]:[01]$/.exec(pid)?.[1] ?? null;
|
|
72
|
+
|
|
73
|
+
/** The cabinet whose corner `pid` is, if it is one. */
|
|
74
|
+
export const cabOfPoint = (sk: Sketch, pid: string): string | null => {
|
|
75
|
+
for (const [id, cb] of Object.entries((sk as WallFace).cabs ?? {})) if (cabPoints(cb).includes(pid)) return id;
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Drop cabinets missing a corner, with their edges. */
|
|
80
|
+
export function gcCabinets(face: WallFace): void {
|
|
81
|
+
if (!face.cabs) { face.cabs = {}; return; }
|
|
82
|
+
for (const id in face.cabs) {
|
|
83
|
+
const cb = face.cabs[id];
|
|
84
|
+
if (cabPoints(cb).some(p => !face.points[p])) {
|
|
85
|
+
delete face.cabs[id];
|
|
86
|
+
for (const l in face.lines) if (face.lines[l].cab === id) delete face.lines[l];
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const k of ['w', 'h'] as const) if (cb[k] && !face.cons[cb[k]!]) delete cb[k];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Wall `lid` (L inches long) was split at its middle; the new wall `nid` runs from there to the old end. What's
|
|
95
|
+
* drawn past the middle moves to the new wall's faces (each group of joined things as a whole, by where its middle is).
|
|
96
|
+
* Constraints that would join the two walls' faces are dropped. Across a face, x runs from the wall's start (A) seen
|
|
97
|
+
* from side -1 and from its end (B) seen from side 1, so positions are recounted for the shorter walls.
|
|
98
|
+
*/
|
|
99
|
+
export function splitFaces(sk: Sketch, lid: string, nid: string, L: number): void {
|
|
100
|
+
if (!sk.faces) return;
|
|
101
|
+
const half = L / 2;
|
|
102
|
+
for (const side of [1, -1] as const) {
|
|
103
|
+
const key = faceKey(lid, side), face: WallFace | undefined = sk.faces[key];
|
|
104
|
+
if (!face) continue;
|
|
105
|
+
const mirror = side === 1, sOf = (u: number) => (mirror ? L - u : u);
|
|
106
|
+
delete face.wall; // its corners are recounted here: the next placing records them afresh
|
|
107
|
+
// groups of points joined by lines and curves
|
|
108
|
+
const group = new Map<string, number>(), links = new Map<string, string[]>();
|
|
109
|
+
const link = (a: string, b: string) => { (links.get(a) ?? links.set(a, []).get(a)!).push(b); (links.get(b) ?? links.set(b, []).get(b)!).push(a); };
|
|
110
|
+
for (const l of Object.values(face.lines)) if (!l.anchor) link(l.a, l.b);
|
|
111
|
+
for (const a of Object.values(face.arcs)) { link(a.c, a.a); link(a.c, a.b); }
|
|
112
|
+
let n = 0;
|
|
113
|
+
for (const id in face.points) {
|
|
114
|
+
if (group.has(id) || face.points[id].anchor) continue;
|
|
115
|
+
const stack = [id];
|
|
116
|
+
group.set(id, n);
|
|
117
|
+
while (stack.length) for (const q of links.get(stack.pop()!) ?? []) if (!group.has(q) && !face.points[q].anchor) { group.set(q, n); stack.push(q); }
|
|
118
|
+
n++;
|
|
119
|
+
}
|
|
120
|
+
const sum = new Array<number>(n).fill(0), cnt = new Array<number>(n).fill(0);
|
|
121
|
+
for (const [id, g] of group) { sum[g] += sOf(face.points[id].x); cnt[g]++; }
|
|
122
|
+
const moved = new Set([...group].filter(([, g]) => sum[g] / cnt[g] > half).map(([id]) => id));
|
|
123
|
+
const to = faceOf(sk, faceKey(nid, side), true);
|
|
124
|
+
for (const id in face.points) {
|
|
125
|
+
const p = face.points[id];
|
|
126
|
+
if (p.anchor) continue;
|
|
127
|
+
const s = sOf(p.x);
|
|
128
|
+
if (moved.has(id)) { to.points[id] = { ...p, x: mirror ? half - (s - half) : s - half }; delete face.points[id]; }
|
|
129
|
+
else if (mirror) p.x = half - s;
|
|
130
|
+
}
|
|
131
|
+
const usesMoved = (ids: (string | undefined)[]) => ids.some(v => !!v && moved.has(v));
|
|
132
|
+
for (const id in face.lines) { const l = face.lines[id]; if (!l.anchor && usesMoved([l.a, l.b])) { to.lines[id] = l; delete face.lines[id]; } }
|
|
133
|
+
for (const id in face.circles) if (moved.has(face.circles[id].c)) { to.circles[id] = face.circles[id]; delete face.circles[id]; }
|
|
134
|
+
for (const id in face.arcs) if (moved.has(face.arcs[id].c)) { to.arcs[id] = face.arcs[id]; delete face.arcs[id]; }
|
|
135
|
+
for (const id in face.cabs ?? {}) if (moved.has(face.cabs[id].bl)) { to.cabs[id] = face.cabs[id]; delete face.cabs[id]; }
|
|
136
|
+
// what moved still hanging on anchors takes a copy of them along (the new wall's own replace them, or let go)
|
|
137
|
+
for (const l of Object.values(to.lines)) for (const p of [l.a, l.b]) if (face.points[p]?.anchor && !to.points[p]) to.points[p] = { ...face.points[p] };
|
|
138
|
+
for (const id in face.cons) {
|
|
139
|
+
const c: Constraint = face.cons[id];
|
|
140
|
+
const pts = (['a', 'b', 'p'] as const).map(k => c[k]).filter((v): v is string => !!v && !face.points[v]?.anchor);
|
|
141
|
+
const ls = (['l', 'l1', 'l2', 'line'] as const).map(k => c[k]).filter((v): v is string => !!v && !face.lines[v]?.anchor);
|
|
142
|
+
const cs = (['c', 'c1', 'c2'] as const).map(k => c[k]).filter((v): v is string => !!v);
|
|
143
|
+
const inNew = (v: string) => moved.has(v) || !!to.lines[v] || !!to.circles[v] || !!to.arcs[v];
|
|
144
|
+
const refs = [...pts, ...ls, ...cs];
|
|
145
|
+
if (!refs.some(inNew)) continue;
|
|
146
|
+
if (refs.every(inNew)) {
|
|
147
|
+
to.cons[id] = c;
|
|
148
|
+
for (const k of ['a', 'b', 'p'] as const) { const v = c[k]; if (v && face.points[v]?.anchor && !to.points[v]) to.points[v] = { ...face.points[v] }; }
|
|
149
|
+
}
|
|
150
|
+
delete face.cons[id];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|