@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/render/draw.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { ed, isSel, type HitChip } from '../app/state';
|
|
2
|
-
import { curveDefined, isDim, lineDefined, measure } from '../model/constraints';
|
|
2
|
+
import { consPoints, curveDefined, isDim, lineDefined, measure } from '../model/constraints';
|
|
3
3
|
import { hyp, norm2, normAng, projT } from '../model/geom';
|
|
4
|
-
import { drivingDim, edgePoint, measureOpeningDim, refPoint, refS } from '../model/openings';
|
|
5
|
-
import { arcAngles, gridSteps, OPENING_TICK_EXT, openingFrame, outsideSide } from '../model/planGeom';
|
|
4
|
+
import { drivingDim, edgePoint, measureOpeningDim, refPoint, refS, widthEntered } from '../model/openings';
|
|
5
|
+
import { arcAngles, curveFrame, dimEnds, dimRadius, edgeOf, gridSteps, OPENING_TICK_EXT, openingFrame, outsideSide, wallThick } from '../model/planGeom';
|
|
6
|
+
import { faceCorner } from '../model/wallfaces';
|
|
6
7
|
import { roomGeom } from '../model/rooms';
|
|
8
|
+
import { wallPieces } from '../model/wallshape';
|
|
9
|
+
import { cabinetFootprints } from '../model/facesolve';
|
|
7
10
|
import { settings } from '../model/settings';
|
|
8
|
-
import { curveInfo,
|
|
9
|
-
import { page, SK } from '../model/store';
|
|
10
|
-
import
|
|
11
|
+
import { curveInfo, frameMemo, lineBetween, roomValid, withLineIndex } from '../model/sketch';
|
|
12
|
+
import { page, planSK, SK } from '../model/store';
|
|
13
|
+
import { elev, elevOpen } from '../editor/elevation';
|
|
14
|
+
import { cabPoints } from '../model/faces';
|
|
15
|
+
import { drawKeyPlan, drawWallBackdrop, drawWallChips, drawWallSelection, type ElevAct } from './elevation';
|
|
16
|
+
import type { Constraint, Page, Sketch, WallFace, XY } from '../model/types';
|
|
11
17
|
import { fmtAng, fmtArea, fmtLen } from '../units';
|
|
12
18
|
import { FONT, TH } from './theme';
|
|
13
|
-
import { cvs, imgCache, mainT, noteHandles, S, sketchBounds, underlayH, underlayHandles, type Xform } from './view';
|
|
19
|
+
import { cvs, imgCache, keyPlanSize, mainT, nextThumbFrame, noteFont, noteHandles, noteTextBox, S, sketchBounds, underlayH, underlayHandles, underlayMid, type Xform } from './view';
|
|
14
20
|
import type { DrawCtx } from './ctx';
|
|
21
|
+
import { dashFor, isSegment, NOTE_TEXT_SIZE, noteLook, shapeOutline } from '../model/markup';
|
|
22
|
+
import { strokeMarkup, strokeOutline } from './markup';
|
|
23
|
+
import { drawLink } from './linkTile';
|
|
24
|
+
import { isLink, linkInfoOf } from '../model/links';
|
|
25
|
+
import { smoothRectPath } from './squircle';
|
|
15
26
|
|
|
16
27
|
type C2D = DrawCtx;
|
|
17
28
|
|
|
@@ -19,34 +30,52 @@ type C2D = DrawCtx;
|
|
|
19
30
|
let hits: HitChip[] | null = null;
|
|
20
31
|
const addHit = (h: HitChip): void => { if (hits) hits.push(h); };
|
|
21
32
|
|
|
22
|
-
const OPENING_PAD = 4;
|
|
23
33
|
|
|
34
|
+
/** A rounded rectangle's path, its corners smoothed as Figma's 60% ("iOS"): render/squircle.ts. */
|
|
24
35
|
export function rr(c: C2D, x: number, y: number, w: number, h: number, r: number): void {
|
|
25
|
-
|
|
26
|
-
c.
|
|
27
|
-
c
|
|
28
|
-
c.arcTo(x + w, y + h, x, y + h, r);
|
|
29
|
-
c.arcTo(x, y + h, x, y, r);
|
|
30
|
-
c.arcTo(x, y, x + w, y, r);
|
|
31
|
-
c.closePath();
|
|
36
|
+
// (one shape for each size, moved into place where the engine can: hundreds of these are drawn every frame)
|
|
37
|
+
if (c.shape) c.shape('rr ' + w + ' ' + h + ' ' + r, x, y, (cc, X, Y) => smoothRectPath(cc, X, Y, w, h, r));
|
|
38
|
+
else smoothRectPath(c, x, y, w, h, r);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface ChipOpts {
|
|
35
42
|
bg: string; fg: string; border?: [string, number?] | null; font?: string; pad?: number; h?: number; minW?: number;
|
|
36
43
|
kind?: HitChip['kind']; id?: string; rot?: number;
|
|
44
|
+
/** a link mark before the text (a value set elsewhere: another view, or a measurement of the job) */
|
|
45
|
+
mark?: 'link';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Blue for outlined (linked) chips: the lighter blue on a dark canvas, so the text stays readable. */
|
|
49
|
+
export function linkBlue(): string {
|
|
50
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(TH.bg);
|
|
51
|
+
const dark = !!m && (parseInt(m[1], 16) * 299 + parseInt(m[2], 16) * 587 + parseInt(m[3], 16) * 114) / 1000 < 128;
|
|
52
|
+
return dark ? TH.lblue : TH.blue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A small chain-link mark centred on (x, y). */
|
|
56
|
+
export function linkMark(c: C2D, x: number, y: number, color: string): void {
|
|
57
|
+
c.save();
|
|
58
|
+
c.translate(x, y); c.rotate(-Math.PI / 4);
|
|
59
|
+
c.strokeStyle = color; c.lineWidth = 1.4;
|
|
60
|
+
c.beginPath(); c.roundRect(-6.5, -2.25, 7.5, 4.5, 2.25); c.roundRect(-1, -2.25, 7.5, 4.5, 2.25); c.stroke();
|
|
61
|
+
c.restore();
|
|
37
62
|
}
|
|
38
63
|
|
|
64
|
+
/** What a link mark takes before a chip's text. */
|
|
65
|
+
const MARK_W = 14;
|
|
66
|
+
|
|
39
67
|
/** Rounded label on the canvas; registers a hit region when `kind` is given. */
|
|
40
|
-
export function chip(c: C2D, text: string, cx: number, cy: number, { bg, fg, border, font = '600 12px', pad = 7, h = 22, minW = 0, kind, id, rot = 0 }: ChipOpts): number {
|
|
68
|
+
export function chip(c: C2D, text: string, cx: number, cy: number, { bg, fg, border, font = '600 12px', pad = 7, h = 22, minW = 0, kind, id, rot = 0, mark }: ChipOpts): number {
|
|
41
69
|
c.font = font + ' ' + FONT;
|
|
42
|
-
const w = Math.max(minW, c.measureText(text).width + pad * 2);
|
|
70
|
+
const mw = mark ? MARK_W : 0, w = Math.max(minW, c.measureText(text).width + pad * 2 + mw);
|
|
43
71
|
const x = cx - w / 2, y = cy - h / 2;
|
|
44
72
|
c.save();
|
|
45
73
|
if (rot) { c.translate(cx, cy); c.rotate(rot); c.translate(-cx, -cy); }
|
|
46
74
|
rr(c, x, y, w, h, 5);
|
|
47
75
|
c.fillStyle = bg; c.fill();
|
|
48
76
|
if (border) { c.lineWidth = border[1] || 1; c.strokeStyle = border[0]; c.stroke(); }
|
|
49
|
-
c.fillStyle = fg; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(text, cx, cy + 0.5);
|
|
77
|
+
c.fillStyle = fg; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(text, cx + mw / 2, cy + 0.5);
|
|
78
|
+
if (mark) linkMark(c, x + pad + 4.5, cy, fg);
|
|
50
79
|
c.restore();
|
|
51
80
|
if (kind) {
|
|
52
81
|
const ext = rot ? Math.max(w, h) / 2 : 0;
|
|
@@ -55,29 +84,37 @@ export function chip(c: C2D, text: string, cx: number, cy: number, { bg, fg, bor
|
|
|
55
84
|
return w;
|
|
56
85
|
}
|
|
57
86
|
|
|
58
|
-
|
|
87
|
+
/** The grid over the screen, and `m` px past each edge (see KeepOpts). */
|
|
88
|
+
function drawGrid(c: C2D, T: Xform, m = 0): void {
|
|
59
89
|
const { W, H } = cvs;
|
|
60
90
|
const { minor, major } = gridSteps(T.s);
|
|
61
|
-
const x0 = (
|
|
91
|
+
const x0 = (-m - T.ox) / T.s, x1 = (W + m - T.ox) / T.s, y1 = (T.oy + m) / T.s, y0 = (T.oy - H - m) / T.s;
|
|
62
92
|
const lines = (step: number, color: string, w: number) => {
|
|
63
|
-
|
|
93
|
+
// (too dense to draw: judged on the screen's width, whatever is drawn past it)
|
|
94
|
+
if (W / T.s / step > 600) return;
|
|
64
95
|
c.beginPath(); c.strokeStyle = color; c.lineWidth = w;
|
|
65
|
-
for (let x = Math.floor(x0 / step) * step; x <= x1; x += step) { const sx = Math.round(x * T.s + T.ox) + 0.5; c.moveTo(sx,
|
|
66
|
-
for (let y = Math.floor(y0 / step) * step; y <= y1; y += step) { const sy = Math.round(-y * T.s + T.oy) + 0.5; c.moveTo(
|
|
96
|
+
for (let x = Math.floor(x0 / step) * step; x <= x1; x += step) { const sx = Math.round(x * T.s + T.ox) + 0.5; c.moveTo(sx, -m); c.lineTo(sx, H + m); }
|
|
97
|
+
for (let y = Math.floor(y0 / step) * step; y <= y1; y += step) { const sy = Math.round(-y * T.s + T.oy) + 0.5; c.moveTo(-m, sy); c.lineTo(W + m, sy); }
|
|
67
98
|
c.stroke();
|
|
68
99
|
};
|
|
69
100
|
lines(minor, TH.grid, 1);
|
|
70
101
|
lines(major, TH.grid2, 1);
|
|
71
102
|
}
|
|
72
103
|
|
|
104
|
+
/** The page's photos and plans, in order (the last on top). */
|
|
73
105
|
function drawUnderlay(c: C2D, T: Xform): void {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
if (!settings.layers.underlay) return;
|
|
107
|
+
for (const u of page().underlays) {
|
|
108
|
+
const im = imgCache(u.src);
|
|
109
|
+
if (!im || !im.complete || !im.naturalWidth) continue;
|
|
110
|
+
// drawn about its middle, turned as it's set (clockwise as seen: the canvas turns that way)
|
|
111
|
+
const h = underlayH(u), m = underlayMid(u), [x, y] = S(T, m.x, m.y), w = u.w * T.s, hh = h * T.s;
|
|
112
|
+
c.save(); c.globalAlpha = u.opacity ?? 0.6;
|
|
113
|
+
c.translate(x, y);
|
|
114
|
+
if (u.rot) c.rotate((u.rot * Math.PI) / 180);
|
|
115
|
+
c.drawImage(im, -w / 2, -hh / 2, w, hh);
|
|
116
|
+
c.restore();
|
|
117
|
+
}
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
const inkFor = (defined: boolean, constr: boolean | undefined) => (constr ? TH.constr : defined ? TH.def : TH.ink);
|
|
@@ -106,30 +143,6 @@ function drawRooms(c: C2D, T: Xform, sk: Sketch, thumb?: boolean): void {
|
|
|
106
143
|
}
|
|
107
144
|
}
|
|
108
145
|
|
|
109
|
-
function wallDegree(sk: Sketch): Record<string, number> {
|
|
110
|
-
const deg: Record<string, number> = {};
|
|
111
|
-
for (const id in sk.lines) {
|
|
112
|
-
const l = sk.lines[id];
|
|
113
|
-
if (l.kind !== 'wall') continue;
|
|
114
|
-
deg[l.a] = (deg[l.a] || 0) + 1; deg[l.b] = (deg[l.b] || 0) + 1;
|
|
115
|
-
}
|
|
116
|
-
for (const id in sk.arcs) {
|
|
117
|
-
const a = sk.arcs[id];
|
|
118
|
-
if (a.kind !== 'wall') continue;
|
|
119
|
-
deg[a.a] = (deg[a.a] || 0) + 1; deg[a.b] = (deg[a.b] || 0) + 1;
|
|
120
|
-
}
|
|
121
|
-
return deg;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Wall centerline path, extended at joints by half the thickness so corners close. */
|
|
125
|
-
function wallPath(c: C2D, T: Xform, sk: Sketch, l: Line, deg: Record<string, number>, extra?: number): void {
|
|
126
|
-
const A = sk.points[l.a], B = sk.points[l.b];
|
|
127
|
-
const dx = B.x - A.x, dy = B.y - A.y, L = hyp(dx, dy) || 1e-9, ux = dx / L, uy = dy / L, e = l.thick / 2 + (extra || 0);
|
|
128
|
-
const ea = deg[l.a] > 1 ? e : 0, eb = deg[l.b] > 1 ? e : 0;
|
|
129
|
-
const [x1, y1] = S(T, A.x - ux * ea, A.y - uy * ea), [x2, y2] = S(T, B.x + ux * eb, B.y + uy * eb);
|
|
130
|
-
c.moveTo(x1, y1); c.lineTo(x2, y2);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
146
|
interface CurveWall { id: string; kind: 'circle' | 'arc'; thick: number; constr?: boolean }
|
|
134
147
|
|
|
135
148
|
function curveWalls(sk: Sketch): CurveWall[] {
|
|
@@ -151,46 +164,137 @@ function curveWallPath(c: C2D, T: Xform, sk: Sketch, cw: CurveWall): void {
|
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Cabinets in the plan: each one's footprint against its wall, from the wall's view (read-only — tap one to open its
|
|
169
|
+
* wall). Drawn as cabinetry, not a shape to edit: base cabinets hatched, with a bold front where the doors face the room
|
|
170
|
+
* and their name when there's room for it; wall (upper) cabinets dashed with a cross, as drafted.
|
|
171
|
+
*/
|
|
172
|
+
function drawCabinetFootprints(c: C2D, T: Xform, sk: Sketch): void {
|
|
173
|
+
const fps = cabinetFootprints(sk);
|
|
174
|
+
if (!fps.length) return;
|
|
175
|
+
for (const fp of fps) {
|
|
176
|
+
// pts: along the wall face (0 → 1), then the front, out in the room (2 → 3)
|
|
177
|
+
const q = fp.pts.map(p => S(T, p.x, p.y));
|
|
178
|
+
const path = () => { c.beginPath(); q.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); };
|
|
179
|
+
const hov = ed.hover?.type === 'cabfoot' && ed.hover.id === fp.key + '#' + fp.id;
|
|
180
|
+
c.save();
|
|
181
|
+
if (fp.upper) {
|
|
182
|
+
path(); c.setLineDash([6, 4]); c.strokeStyle = TH.ink2; c.lineWidth = hov ? 2 : 1.2; c.stroke();
|
|
183
|
+
c.beginPath(); c.moveTo(...q[0]); c.lineTo(...q[2]); c.moveTo(...q[1]); c.lineTo(...q[3]); c.lineWidth = 1; c.stroke();
|
|
184
|
+
c.restore();
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
path();
|
|
188
|
+
c.fillStyle = hov ? 'rgba(128,128,128,.30)' : 'rgba(128,128,128,.16)'; c.fill();
|
|
189
|
+
// hatching, clipped to the footprint
|
|
190
|
+
c.save(); path(); c.clip();
|
|
191
|
+
const xs = q.map(p => p[0]), ys = q.map(p => p[1]), x0 = Math.min(...xs), x1 = Math.max(...xs), y0 = Math.min(...ys), y1 = Math.max(...ys);
|
|
192
|
+
c.strokeStyle = TH.ink2; c.globalAlpha = 0.45; c.lineWidth = 1; c.beginPath();
|
|
193
|
+
for (let k = x0 - (y1 - y0); k < x1; k += 7) { c.moveTo(k, y1); c.lineTo(k + (y1 - y0), y0); }
|
|
194
|
+
c.stroke(); c.restore();
|
|
195
|
+
path(); c.strokeStyle = TH.ink2; c.lineWidth = hov ? 2 : 1.2; c.stroke();
|
|
196
|
+
// the front (where its doors are)
|
|
197
|
+
c.beginPath(); c.moveTo(...q[2]); c.lineTo(...q[3]); c.strokeStyle = TH.ink; c.lineWidth = 2.5; c.stroke();
|
|
198
|
+
const w = Math.hypot(q[1][0] - q[0][0], q[1][1] - q[0][1]), d = Math.hypot(q[2][0] - q[1][0], q[2][1] - q[1][1]);
|
|
199
|
+
if (Math.max(w, d) > 64 && Math.min(w, d) > 18) {
|
|
200
|
+
// along its longer side, reading upright
|
|
201
|
+
const [p0, p1] = w >= d ? [q[0], q[1]] : [q[1], q[2]], cx = (q[0][0] + q[2][0]) / 2, cy = (q[0][1] + q[2][1]) / 2;
|
|
202
|
+
let a = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
|
|
203
|
+
if (a > Math.PI / 2) a -= Math.PI; else if (a <= -Math.PI / 2) a += Math.PI;
|
|
204
|
+
c.translate(cx, cy); c.rotate(a);
|
|
205
|
+
c.font = '600 10px ' + FONT; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
206
|
+
c.fillStyle = TH.bg; c.globalAlpha = 0.85; const tw = c.measureText('Cabinet').width; c.fillRect(-tw / 2 - 3, -7, tw + 6, 14);
|
|
207
|
+
c.globalAlpha = 1; c.fillStyle = TH.ink; c.fillText('Cabinet', 0, 0.5);
|
|
208
|
+
}
|
|
209
|
+
c.restore();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A wall piece's outline, added to the current path (`fresh`: start a new path first). */
|
|
214
|
+
function piecePath(c: C2D, T: Xform, pts: XY[], fresh = true): void {
|
|
215
|
+
if (fresh) c.beginPath();
|
|
216
|
+
pts.forEach((p, i) => { const [x, y] = S(T, p.x, p.y); if (i) c.lineTo(x, y); else c.moveTo(x, y); });
|
|
217
|
+
c.closePath();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Walls as solid bands of their thickness, outlined in the definition colour. Straight walls are filled outlines cut
|
|
222
|
+
* to fit where they meet (model/wallshape.ts), so corners at any angle close cleanly; curved walls are thick strokes.
|
|
223
|
+
* Every outline is drawn first and the fill over all of them, so only the outside of the outlines shows, not the seams.
|
|
224
|
+
*/
|
|
154
225
|
function drawWalls(c: C2D, T: Xform, sk: Sketch, thumb?: boolean): void {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
if (!
|
|
158
|
-
c.lineCap = 'butt';
|
|
226
|
+
const shown = (w: { constr?: boolean }) => !(w.constr && !settings.layers.construction);
|
|
227
|
+
const pieces = wallPieces(sk, shown), curves = curveWalls(sk);
|
|
228
|
+
if (!pieces.length && !curves.length) return;
|
|
159
229
|
const ow = thumb ? 1 : 1.6;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
c.lineWidth = l.thick * T.s + ow * 2;
|
|
165
|
-
c.strokeStyle = ed.highlight.has(id) ? TH.red : inkFor(d && !thumb, l.constr);
|
|
166
|
-
c.stroke();
|
|
167
|
-
}
|
|
168
|
-
// curved walls: same outline-then-fill treatment
|
|
169
|
-
const curves = curveWalls(sk);
|
|
230
|
+
const ink = (id: string) => { const l = sk.lines[id]; return ed.highlight.has(id) ? TH.red : inkFor(lineDefined(ed.ana, l) && !thumb, l.constr); };
|
|
231
|
+
c.save();
|
|
232
|
+
c.lineJoin = 'round'; c.lineCap = 'butt';
|
|
233
|
+
for (const p of pieces) { piecePath(c, T, p.pts); c.lineWidth = ow * 2; c.strokeStyle = ink(p.of); c.stroke(); }
|
|
170
234
|
for (const cw of curves) {
|
|
171
235
|
curveWallPath(c, T, sk, cw);
|
|
172
236
|
c.lineWidth = cw.thick * T.s + ow * 2;
|
|
173
237
|
c.strokeStyle = ed.highlight.has(cw.id) ? TH.red : inkFor(curveDefined(ed.ana, sk, cw.id) && !thumb, cw.constr);
|
|
174
238
|
c.stroke();
|
|
175
239
|
}
|
|
176
|
-
for
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
240
|
+
// one fill for every piece: pieces filled one by one leave a hairline where they meet
|
|
241
|
+
c.fillStyle = TH.wall;
|
|
242
|
+
c.beginPath();
|
|
243
|
+
for (const p of pieces) piecePath(c, T, p.pts, false);
|
|
244
|
+
c.fill();
|
|
180
245
|
for (const cw of curves) { curveWallPath(c, T, sk, cw); c.lineWidth = Math.max(0.5, cw.thick * T.s); c.strokeStyle = TH.wall; c.stroke(); }
|
|
181
|
-
if (thumb)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
c.
|
|
186
|
-
c.
|
|
187
|
-
c.stroke();
|
|
246
|
+
if (!thumb) {
|
|
247
|
+
for (const p of pieces) {
|
|
248
|
+
if (!p.id || !(isSel('line', p.id) || (ed.hover && ed.hover.type === 'line' && ed.hover.id === p.id))) continue;
|
|
249
|
+
piecePath(c, T, p.pts);
|
|
250
|
+
c.fillStyle = isSel('line', p.id) ? 'rgba(255,173,0,.55)' : 'rgba(255,173,0,.28)';
|
|
251
|
+
c.fill();
|
|
188
252
|
}
|
|
253
|
+
drawEdges(c, T, sk);
|
|
254
|
+
}
|
|
255
|
+
c.restore();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Wall edges picked on their own (and the one under the pointer): along the face, from its corner to its corner — a
|
|
260
|
+
* curved wall's all along it.
|
|
261
|
+
*/
|
|
262
|
+
function drawEdges(c: C2D, T: Xform, sk: Sketch): void {
|
|
263
|
+
const ids = ed.sel.filter(s => s.type === 'wface').map(s => s.id);
|
|
264
|
+
const hov = ed.hover?.type === 'wface' && !ids.includes(ed.hover.id) ? ed.hover.id : null;
|
|
265
|
+
if (hov) ids.push(hov);
|
|
266
|
+
for (const id of ids) {
|
|
267
|
+
const [lid, side] = edgeOf(id);
|
|
268
|
+
if (!sk.lines[lid]) { drawCurveEdge(c, T, sk, lid, side, id !== hov); continue; }
|
|
269
|
+
const a = faceCorner(sk, lid, side, 'a'), b = faceCorner(sk, lid, side, 'b');
|
|
270
|
+
if (!a || !b) continue;
|
|
271
|
+
const [ax, ay] = S(T, a.x, a.y), [bx, by] = S(T, b.x, b.y), lit = id !== hov;
|
|
272
|
+
c.lineCap = 'round';
|
|
273
|
+
c.strokeStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)'; c.lineWidth = lit ? 3.5 : 2.5;
|
|
274
|
+
c.beginPath(); c.moveTo(ax, ay); c.lineTo(bx, by); c.stroke();
|
|
275
|
+
// (its corners: where a length on it is measured from and to)
|
|
276
|
+
c.fillStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)';
|
|
277
|
+
for (const [x, y] of [[ax, ay], [bx, by]]) { c.beginPath(); c.arc(x, y, lit ? 4 : 3, 0, Math.PI * 2); c.fill(); }
|
|
189
278
|
}
|
|
190
279
|
}
|
|
191
280
|
|
|
281
|
+
/** A curved wall's outer (+1) or inner (−1) edge, picked (`lit`) or under the pointer. */
|
|
282
|
+
function drawCurveEdge(c: C2D, T: Xform, sk: Sketch, id: string, side: 1 | -1, lit: boolean): void {
|
|
283
|
+
const w = sk.circles[id] ?? sk.arcs[id], f = curveFrame(sk, id);
|
|
284
|
+
if (!w || !f) return;
|
|
285
|
+
const rr = (f.r + (side * wallThick(w)) / 2) * T.s, [x, y] = S(T, f.o.x, f.o.y);
|
|
286
|
+
if (rr <= 0) return;
|
|
287
|
+
c.lineCap = 'round';
|
|
288
|
+
c.strokeStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)'; c.lineWidth = lit ? 3.5 : 2.5;
|
|
289
|
+
c.beginPath();
|
|
290
|
+
if (f.arc) c.arc(x, y, rr, -f.arc.t0, -f.arc.t1, true); else c.arc(x, y, rr, 0, Math.PI * 2);
|
|
291
|
+
c.stroke();
|
|
292
|
+
}
|
|
293
|
+
|
|
192
294
|
function drawOpenings(c: C2D, T: Xform, sk: Sketch): void {
|
|
193
|
-
|
|
295
|
+
// the selected or hovered one last: a neighbour drawn after it would knock the wall out over its outline
|
|
296
|
+
const lit = (id: string) => isSel('opening', id) || (ed.hover?.type === 'opening' && ed.hover.id === id);
|
|
297
|
+
for (const id of Object.keys(sk.openings).sort((a, b) => Number(lit(a)) - Number(lit(b)))) {
|
|
194
298
|
const o = sk.openings[id], g = openingFrame(sk, id);
|
|
195
299
|
if (!g) continue;
|
|
196
300
|
const { A, ux, uy, nx, ny, s0, s1, w, t } = g, f = o.flip ? -1 : 1;
|
|
@@ -207,10 +311,10 @@ function drawOpenings(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
207
311
|
const sel1 = isSel('opening', id);
|
|
208
312
|
const hov = !sel1 && ed.hover && ed.hover.type === 'opening' && ed.hover.id === id;
|
|
209
313
|
const ext = OPENING_TICK_EXT / T.s;
|
|
210
|
-
// hover / selection band so it's obvious what a click will grab
|
|
314
|
+
// hover / selection band so it's obvious what a click will grab: exactly its footprint in the wall, face to face
|
|
315
|
+
// and end to end (not padded out past them)
|
|
211
316
|
if (sel1 || hov) {
|
|
212
|
-
|
|
213
|
-
quad(s0 - pad, s1 + pad, t / 2 + ext + pad);
|
|
317
|
+
quad(s0, s1, t / 2);
|
|
214
318
|
c.fillStyle = sel1 ? 'rgba(255,173,0,.30)' : 'rgba(255,173,0,.16)'; c.fill();
|
|
215
319
|
c.lineWidth = sel1 ? 2 : 1.5; c.strokeStyle = sel1 ? TH.yellow : 'rgba(255,173,0,.85)'; c.stroke();
|
|
216
320
|
}
|
|
@@ -255,6 +359,7 @@ function drawLines(c: C2D, T: Xform, sk: Sketch, thumb?: boolean): void {
|
|
|
255
359
|
c.beginPath(); c.moveTo(x1, y1); c.lineTo(x2, y2);
|
|
256
360
|
c.lineWidth = 7; c.strokeStyle = selected ? 'rgba(255,173,0,.6)' : 'rgba(255,173,0,.3)'; c.stroke();
|
|
257
361
|
}
|
|
362
|
+
if (l.anchor || l.cab) continue; // a wall face's edge from the plan, a cabinet's: the wall view's backdrop draws them
|
|
258
363
|
c.beginPath(); c.moveTo(x1, y1); c.lineTo(x2, y2);
|
|
259
364
|
if (l.constr) { c.setLineDash([6, 5]); c.lineWidth = 1.2; } else c.lineWidth = thumb ? 1.2 : 2;
|
|
260
365
|
c.strokeStyle = ed.highlight.has(id) ? TH.red : inkFor(lineDefined(ed.ana, l) && !thumb, l.constr);
|
|
@@ -297,9 +402,15 @@ function drawCurves(c: C2D, T: Xform, sk: Sketch, thumb?: boolean): void {
|
|
|
297
402
|
}
|
|
298
403
|
}
|
|
299
404
|
|
|
405
|
+
/** Points on a line that isn't fully defined: drawn in ink, not green (worked out once a frame, not once a point). */
|
|
406
|
+
const looseEnds = (sk: Sketch): Set<string> => frameMemo(sk, 'looseEnds', () => {
|
|
407
|
+
const out = new Set<string>();
|
|
408
|
+
for (const lid in sk.lines) { const l = sk.lines[lid]; if (!lineDefined(ed.ana, l)) { out.add(l.a); out.add(l.b); } }
|
|
409
|
+
return out;
|
|
410
|
+
});
|
|
411
|
+
|
|
300
412
|
function inkForPoint(sk: Sketch, id: string): string {
|
|
301
|
-
|
|
302
|
-
return TH.def;
|
|
413
|
+
return looseEnds(sk).has(id) ? TH.ink : TH.def;
|
|
303
414
|
}
|
|
304
415
|
|
|
305
416
|
function drawLock(c: C2D, x: number, y: number): void {
|
|
@@ -316,6 +427,8 @@ function drawLock(c: C2D, x: number, y: number): void {
|
|
|
316
427
|
function drawPoints(c: C2D, T: Xform, sk: Sketch): void {
|
|
317
428
|
const { W, H } = cvs;
|
|
318
429
|
const touch = ed.lastPointerTouch;
|
|
430
|
+
// a cabinet's corners aren't grab points: it moves as one, like a door or window
|
|
431
|
+
const cabCorners = new Set(Object.values((sk as WallFace).cabs ?? {}).flatMap(cabPoints));
|
|
319
432
|
for (const id in sk.points) {
|
|
320
433
|
const p = sk.points[id];
|
|
321
434
|
const [x, y] = S(T, p.x, p.y);
|
|
@@ -325,13 +438,16 @@ function drawPoints(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
325
438
|
c.beginPath(); c.moveTo(x - 9, y); c.lineTo(x + 9, y); c.moveTo(x, y - 9); c.lineTo(x, y + 9); c.stroke();
|
|
326
439
|
}
|
|
327
440
|
const selected = isSel('point', id), hov = ed.hover && ed.hover.type === 'point' && ed.hover.id === id;
|
|
441
|
+
// a wall face's corners from the plan, and a cabinet's, show only when picked or under the pointer (like a window's)
|
|
442
|
+
if ((p.anchor || cabCorners.has(id)) && !selected && !hov) continue;
|
|
328
443
|
const r = (touch ? 5 : 4.2) + (hov ? 1.5 : 0);
|
|
444
|
+
// (a door's or window's corners on a wall face are free until the plan locks it, or the face pins it: as a cabinet's)
|
|
329
445
|
const det = ed.ana.det.has(id);
|
|
330
446
|
c.beginPath(); c.arc(x, y, r, 0, Math.PI * 2);
|
|
331
447
|
if (selected) { c.fillStyle = TH.yellow; c.fill(); c.lineWidth = 1.5; c.strokeStyle = TH.ink; c.stroke(); }
|
|
332
448
|
else if (det) { c.fillStyle = ed.highlight.has(id) ? TH.red : p.origin ? TH.ink : inkForPoint(sk, id); c.fill(); }
|
|
333
449
|
else { c.fillStyle = TH.bg; c.fill(); c.lineWidth = 1.8; c.strokeStyle = TH.ink; c.stroke(); }
|
|
334
|
-
if (p.fixed && !p.origin) drawLock(c, x + 9, y - 14);
|
|
450
|
+
if (p.fixed && !p.origin && !p.anchor) drawLock(c, x + 9, y - 14);
|
|
335
451
|
}
|
|
336
452
|
}
|
|
337
453
|
|
|
@@ -343,21 +459,23 @@ type DimAnchor =
|
|
|
343
459
|
function dimAnchor(sk: Sketch, c: Constraint): DimAnchor | null {
|
|
344
460
|
const P = (id: string) => sk.points[id];
|
|
345
461
|
if (c.type === 'dist') {
|
|
346
|
-
|
|
462
|
+
// (measured on a wall's face: from its corner to corner, the label off the face on that side)
|
|
463
|
+
const e = dimEnds(sk, c);
|
|
464
|
+
if (!e) return null;
|
|
465
|
+
const { a, b } = e;
|
|
347
466
|
const dx = b.x - a.x, dy = b.y - a.y, L = hyp(dx, dy) || 1e-9, nx = -dy / L, ny = dx / L, off = c.off || 0;
|
|
348
467
|
return { m: { x: (a.x + b.x) / 2 + nx * off, y: (a.y + b.y) / 2 + ny * off }, a, b, nx, ny, L };
|
|
349
468
|
}
|
|
350
469
|
if (c.type === 'pldist') {
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
return { m: { x: (
|
|
470
|
+
// (between walls' faces: from face to face)
|
|
471
|
+
const e = dimEnds(sk, c);
|
|
472
|
+
if (!e) return null;
|
|
473
|
+
return { m: { x: (e.a.x + e.b.x) / 2, y: (e.a.y + e.b.y) / 2 }, a: e.a, b: e.b };
|
|
355
474
|
}
|
|
356
475
|
if (c.type === 'diam') {
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
return { m: { x: o.x, y: o.y }, o, r };
|
|
476
|
+
// (across a curved wall's face: its arrows on that face)
|
|
477
|
+
const g = dimRadius(sk, c);
|
|
478
|
+
return g ? { m: { x: g.o.x, y: g.o.y }, o: g.o, r: g.r } : null;
|
|
361
479
|
}
|
|
362
480
|
if (c.type === 'angle') {
|
|
363
481
|
const l1 = sk.lines[c.l1!], l2 = sk.lines[c.l2!];
|
|
@@ -373,20 +491,38 @@ function dimAnchor(sk: Sketch, c: Constraint): DimAnchor | null {
|
|
|
373
491
|
return null;
|
|
374
492
|
}
|
|
375
493
|
|
|
376
|
-
/**
|
|
494
|
+
/** Whether a dimension, or something it measures (its lines, points or curves), is selected or hovered. */
|
|
495
|
+
function dimLive(sk: Sketch, id: string, d: Constraint): boolean {
|
|
496
|
+
const pts = consPoints(sk, d), lines = new Set([d.line, d.l, d.l1, d.l2].filter(Boolean)), curves = new Set([d.c, d.c1, d.c2].filter(Boolean));
|
|
497
|
+
const hit = (x: { type: string; id: string } | null) => !!x && (
|
|
498
|
+
((x.type === 'dim' || x.type === 'cons') && x.id === id) || (x.type === 'point' && pts.has(x.id)) || (x.type === 'line' && lines.has(x.id))
|
|
499
|
+
|| ((x.type === 'circle' || x.type === 'arc') && curves.has(x.id)));
|
|
500
|
+
return ed.sel.some(hit) || hit(ed.hover);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Driving/reference dimension chips. (Lengths are added from the bottom bar's + / Length, not on-canvas prompts.) What's
|
|
505
|
+
* been entered (blue, or red when it can't be met) always shows; a reference only while it or what it measures is
|
|
506
|
+
* selected or hovered.
|
|
507
|
+
*/
|
|
377
508
|
function drawDims(c: C2D, T: Xform, sk: Sketch): void {
|
|
378
509
|
if (!settings.layers.dims) return;
|
|
379
510
|
for (const id in sk.cons) {
|
|
380
511
|
const d = sk.cons[id];
|
|
381
|
-
if (!isDim(d) || d.layer === 'print') continue;
|
|
512
|
+
if (!isDim(d) || d.layer === 'print' || d.elev) continue; // the wall view's own dimensions are its chips
|
|
382
513
|
const A = dimAnchor(sk, d);
|
|
383
514
|
if (!A) continue;
|
|
384
515
|
const ref = d.driving === false, val = ref ? measure(sk, d) : d.value!;
|
|
385
516
|
const selected = isSel('dim', id) || isSel('cons', id);
|
|
386
517
|
const bad = ed.highlight.has(id) || ed.ana.bad.has(id);
|
|
518
|
+
if (ref && !bad && !dimLive(sk, id, d)) continue;
|
|
519
|
+
// a measurement of the job sets it: outlined, with a link mark — red once the job's measurement is gone
|
|
520
|
+
const linked = !!d.measurementRef && !ref && !bad, gone = linked && linkInfoOf(d.measurementRef!)?.exists === false;
|
|
387
521
|
let bg = ref ? TH.ref : TH.blue, fg = ref ? TH.reftext : '#fff';
|
|
388
522
|
if (bad) { bg = TH.red; fg = '#fff'; }
|
|
389
|
-
|
|
523
|
+
if (linked) { bg = TH.bg; fg = gone ? TH.red : d.type === 'angle' ? TH.purple : linkBlue(); }
|
|
524
|
+
const border: [string, number] | null = selected ? [TH.yellow, 2.5] : linked ? [fg, 1.5] : null;
|
|
525
|
+
const mark = linked ? 'link' as const : undefined;
|
|
390
526
|
const wrap = (s: string) => (ref ? '(' : '') + s + (ref ? ')' : '');
|
|
391
527
|
if (d.type === 'angle' && 'vtx' in A) {
|
|
392
528
|
const [vx, vy] = S(T, A.vtx.x, A.vtx.y);
|
|
@@ -397,26 +533,29 @@ function drawDims(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
397
533
|
while (sweep > Math.PI) sweep -= 2 * Math.PI;
|
|
398
534
|
while (sweep < -Math.PI) sweep += 2 * Math.PI;
|
|
399
535
|
c.save(); c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.purple; c.lineWidth = 1.2;
|
|
536
|
+
if (ref) c.setLineDash([3, 3]);
|
|
400
537
|
c.beginPath(); c.arc(vx, vy, 22, t1, t1 + sweep, sweep < 0); c.stroke(); c.restore();
|
|
401
538
|
}
|
|
402
|
-
chip(c, wrap(fmtAng(val)), vx + A.bis[0] * 50, vy - A.bis[1] * 50,
|
|
539
|
+
chip(c, wrap(fmtAng(val)), vx + A.bis[0] * 50, vy - A.bis[1] * 50, linked ? { bg, fg, border, kind: 'dim', id, mark }
|
|
540
|
+
: { bg: bad ? TH.red : ref ? TH.ref : TH.purple, fg: ref ? TH.reftext : '#fff', border, kind: 'dim', id });
|
|
403
541
|
continue;
|
|
404
542
|
}
|
|
405
543
|
if (d.type === 'diam' && 'o' in A) {
|
|
406
|
-
drawDiameter(c, T, A.o, A.r, d, 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id });
|
|
544
|
+
drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id, mark }, ref);
|
|
407
545
|
continue;
|
|
408
546
|
}
|
|
409
547
|
if (!('a' in A)) continue;
|
|
410
548
|
const [mx, my] = S(T, A.m.x, A.m.y);
|
|
411
549
|
const lineColor = bad ? TH.red : ref ? TH.chipline : TH.blue;
|
|
412
550
|
if (d.type === 'dist' && A.nx != null && Math.abs((d.off || 0) * T.s) > 10) {
|
|
413
|
-
drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny!, d.off!, lineColor);
|
|
551
|
+
drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny!, d.off!, lineColor, ref);
|
|
414
552
|
} else if (d.type === 'pldist' || !d.line) {
|
|
553
|
+
// (dashed only when it's a reference: a dashed line means the number holds nothing, wherever it's drawn)
|
|
415
554
|
const [ax, ay] = S(T, A.a.x, A.a.y), [bx, by] = S(T, A.b.x, A.b.y);
|
|
416
|
-
c.save(); c.setLineDash([3, 3]); c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue; c.lineWidth = 1.2;
|
|
555
|
+
c.save(); if (ref) c.setLineDash([3, 3]); c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue; c.lineWidth = 1.2;
|
|
417
556
|
c.beginPath(); c.moveTo(ax, ay); c.lineTo(bx, by); c.stroke(); c.restore();
|
|
418
557
|
}
|
|
419
|
-
chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id });
|
|
558
|
+
chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id, mark });
|
|
420
559
|
}
|
|
421
560
|
}
|
|
422
561
|
|
|
@@ -482,6 +621,8 @@ function drawOpeningDims(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
482
621
|
for (const d of o.dims || []) {
|
|
483
622
|
const r = refS(sk, o, d.ref), rp = refPoint(sk, d.ref);
|
|
484
623
|
if (r == null || !rp) continue;
|
|
624
|
+
// a reference shows only while its door or window is selected or hovered; what locks it always
|
|
625
|
+
if (!d.driving && !ed.badOpeningDims.has(d.id) && !active) continue;
|
|
485
626
|
const e = d.edge === 's0' ? g.s0 : g.s1;
|
|
486
627
|
const k = d.off ?? kRow(place(r, e));
|
|
487
628
|
const bad = ed.badOpeningDims.has(d.id);
|
|
@@ -504,13 +645,15 @@ function drawOpeningDims(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
504
645
|
// blue is only for dimensions that lock something
|
|
505
646
|
if (active && !locked) {
|
|
506
647
|
const grey = TH.chipline;
|
|
507
|
-
if (g.s0 > 0.5) { ext(0, kw, grey); span(0, g.s0, kw, grey); label(0, g.s0, 'a', fmtLen(g.s0), kw, TH.ref, TH.reftext); }
|
|
508
|
-
if (L - g.s1 > 0.5) { ext(L, kw, grey); span(g.s1, L, kw, grey); label(g.s1, L, 'b', fmtLen(L - g.s1), kw, TH.ref, TH.reftext); }
|
|
648
|
+
if (g.s0 > 0.5) { ext(0, kw, grey); span(0, g.s0, kw, grey); label(0, g.s0, 'a', '(' + fmtLen(g.s0) + ')', kw, TH.ref, TH.reftext); }
|
|
649
|
+
if (L - g.s1 > 0.5) { ext(L, kw, grey); span(g.s1, L, kw, grey); label(g.s1, L, 'b', '(' + fmtLen(L - g.s1) + ')', kw, TH.ref, TH.reftext); }
|
|
509
650
|
}
|
|
510
|
-
// width: a
|
|
511
|
-
//
|
|
512
|
-
if (o
|
|
513
|
-
|
|
651
|
+
// width: a full dimension, a solid line with extension lines from the jambs; blue once it's been entered (and then
|
|
652
|
+
// always shown), grey while it's the standard size it was placed at (and then only while the opening is selected or hovered)
|
|
653
|
+
if (!widthEntered(o) && !active) continue;
|
|
654
|
+
const wc = widthEntered(o) ? TH.blue : TH.chipline;
|
|
655
|
+
ext(g.s0, kw, wc); ext(g.s1, kw, wc); span(g.s0, g.s1, kw, wc, false);
|
|
656
|
+
label(g.s0, g.s1, 'w', fmtLen(g.w), kw, widthEntered(o) ? TH.blue : TH.ref, widthEntered(o) ? '#fff' : TH.reftext, isSel('opening', id) ? [TH.yellow, 2] : null);
|
|
514
657
|
}
|
|
515
658
|
}
|
|
516
659
|
|
|
@@ -522,7 +665,8 @@ function drawOpeningAnchors(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
522
665
|
if (ed.mode !== 'draft') return;
|
|
523
666
|
for (const id in sk.openings) {
|
|
524
667
|
const o = sk.openings[id];
|
|
525
|
-
const
|
|
668
|
+
const endPicked = (e: string) => isSel('oanchor', id + '|' + e);
|
|
669
|
+
const show = ed.tool === 'dim' || isSel('opening', id) || endPicked('s0') || endPicked('s1') || (ed.hover != null && ed.hover.id === id);
|
|
526
670
|
if (!show) continue;
|
|
527
671
|
const locked = !!drivingDim(o);
|
|
528
672
|
for (const edge of ['s0', 's1'] as const) {
|
|
@@ -531,7 +675,7 @@ function drawOpeningAnchors(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
531
675
|
const [x, y] = S(T, p.x, p.y);
|
|
532
676
|
const hov = ed.hover && ed.hover.type === 'oanchor' && ed.hover.id === id && ed.hover.part === edge;
|
|
533
677
|
// a body pick hasn't settled on an edge yet, so both light up
|
|
534
|
-
const picked = ed.pickedAnchor && ed.pickedAnchor.id === id && (ed.pickedAnchor.body || ed.pickedAnchor.edge === edge);
|
|
678
|
+
const picked = (ed.pickedAnchor && ed.pickedAnchor.id === id && (ed.pickedAnchor.body || ed.pickedAnchor.edge === edge)) || endPicked(edge);
|
|
535
679
|
const r = (ed.lastPointerTouch ? 5 : 4.2) + (hov ? 1.5 : 0);
|
|
536
680
|
c.save();
|
|
537
681
|
c.beginPath(); c.rect(x - r, y - r, r * 2, r * 2);
|
|
@@ -614,7 +758,7 @@ export function drawPendingDim(c: C2D, T: Xform, p: { from: XY; to: XY; a: XY; b
|
|
|
614
758
|
* A linear dimension pulled off its line: extension lines from both ends out to a dimension line with
|
|
615
759
|
* arrowheads, so the label always reads as attached to what it measures.
|
|
616
760
|
*/
|
|
617
|
-
function drawDimLeaders(c: C2D, T: Xform, a: XY, b: XY, nx: number, ny: number, off: number, color: string): void {
|
|
761
|
+
function drawDimLeaders(c: C2D, T: Xform, a: XY, b: XY, nx: number, ny: number, off: number, color: string, dashed = false): void {
|
|
618
762
|
const [ax, ay] = S(T, a.x, a.y), [bx, by] = S(T, b.x, b.y);
|
|
619
763
|
const [qax, qay] = S(T, a.x + nx * off, a.y + ny * off), [qbx, qby] = S(T, b.x + nx * off, b.y + ny * off);
|
|
620
764
|
const [ex, ey] = norm2(qax - ax, qay - ay); // screen direction from geometry toward the dimension line
|
|
@@ -625,7 +769,9 @@ function drawDimLeaders(c: C2D, T: Xform, a: XY, b: XY, nx: number, ny: number,
|
|
|
625
769
|
for (const [x0, y0, x1, y1] of [[ax, ay, qax, qay], [bx, by, qbx, qby]]) { c.moveTo(x0 + ex * gap, y0 + ey * gap); c.lineTo(x1 + ex * over, y1 + ey * over); }
|
|
626
770
|
c.stroke();
|
|
627
771
|
c.lineWidth = 1.2;
|
|
772
|
+
if (dashed) c.setLineDash([3, 3]);
|
|
628
773
|
c.beginPath(); c.moveTo(qax, qay); c.lineTo(qbx, qby); c.stroke();
|
|
774
|
+
c.setLineDash([]);
|
|
629
775
|
const [ux, uy] = norm2(qbx - qax, qby - qay);
|
|
630
776
|
arrowHead(c, [qbx, qby], ux, uy); arrowHead(c, [qax, qay], -ux, -uy);
|
|
631
777
|
c.restore();
|
|
@@ -635,7 +781,7 @@ function drawDimLeaders(c: C2D, T: Xform, a: XY, b: XY, nx: number, ny: number,
|
|
|
635
781
|
* Diameter dimension, CAD style: a line across the circle through its centre with arrowheads on the
|
|
636
782
|
* circle, a leader out past the edge and a short landing with the label, so the centre stays free to grab.
|
|
637
783
|
*/
|
|
638
|
-
function drawDiameter(c: C2D, T: Xform, o: XY, r: number, d: Constraint, text: string, color: string, chipOpts: ChipOpts): void {
|
|
784
|
+
function drawDiameter(c: C2D, T: Xform, o: XY, r: number, d: Constraint, text: string, color: string, chipOpts: ChipOpts, dashed = false): void {
|
|
639
785
|
const a = d.ang ?? DIAM_DEFAULT_ANG, ux = Math.cos(a), uy = Math.sin(a);
|
|
640
786
|
const [ox, oy] = S(T, o.x, o.y);
|
|
641
787
|
const rs = r * T.s, sx = ux, sy = -uy; // screen direction (y down)
|
|
@@ -646,11 +792,16 @@ function drawDiameter(c: C2D, T: Xform, o: XY, r: number, d: Constraint, text: s
|
|
|
646
792
|
const end = [q[0] + side * land, q[1]];
|
|
647
793
|
c.save();
|
|
648
794
|
c.strokeStyle = color; c.fillStyle = color; c.lineWidth = 1.2;
|
|
649
|
-
|
|
650
|
-
|
|
795
|
+
// a radius (a fillet's) runs from the centre out through the arc; a diameter right across
|
|
796
|
+
const from = d.rad ? [ox, oy] : p2;
|
|
797
|
+
if (dashed) c.setLineDash([3, 3]);
|
|
798
|
+
c.beginPath(); c.moveTo(from[0], from[1]); c.lineTo(q[0], q[1]); c.lineTo(end[0], end[1]); c.stroke();
|
|
799
|
+
c.setLineDash([]);
|
|
800
|
+
arrowHead(c, p1, sx, sy);
|
|
801
|
+
if (!d.rad) arrowHead(c, p2, -sx, -sy);
|
|
651
802
|
c.restore();
|
|
652
803
|
c.font = (chipOpts.font || '600 12px') + ' ' + FONT;
|
|
653
|
-
const w = c.measureText(text).width + 14;
|
|
804
|
+
const w = c.measureText(text).width + 14 + (chipOpts.mark ? MARK_W : 0);
|
|
654
805
|
chip(c, text, end[0] + side * (w / 2 + 2), end[1], chipOpts);
|
|
655
806
|
}
|
|
656
807
|
|
|
@@ -697,6 +848,21 @@ function drawPrintDims(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
697
848
|
}
|
|
698
849
|
}
|
|
699
850
|
|
|
851
|
+
/**
|
|
852
|
+
* A badge's symbol drawn as lines rather than a character: ∥ ⊥ ∘ ◎ aren't in every typeface — on a device the
|
|
853
|
+
* app's own (Inter) lacks them and Skia has nothing to fall back on, so they came out as the font's "missing glyph"
|
|
854
|
+
* box. Letters and = — are in every font and stay text. False for a symbol that isn't one of these.
|
|
855
|
+
*/
|
|
856
|
+
function drawSymbol(c: C2D, g: string, x: number, y: number, ink: string): boolean {
|
|
857
|
+
c.strokeStyle = ink; c.lineWidth = 1.6; c.lineCap = 'round';
|
|
858
|
+
c.beginPath();
|
|
859
|
+
if (g === '∥') { c.moveTo(x - 2.5, y - 5); c.lineTo(x - 2.5, y + 5); c.moveTo(x + 2.5, y - 5); c.lineTo(x + 2.5, y + 5); c.stroke(); return true; }
|
|
860
|
+
if (g === '⊥') { c.moveTo(x, y - 5); c.lineTo(x, y + 4.5); c.moveTo(x - 5, y + 4.5); c.lineTo(x + 5, y + 4.5); c.stroke(); return true; }
|
|
861
|
+
if (g === '∘') { c.arc(x, y, 3, 0, Math.PI * 2); c.stroke(); return true; }
|
|
862
|
+
if (g === '◎') { c.lineWidth = 1.3; c.arc(x, y, 4.5, 0, Math.PI * 2); c.stroke(); c.beginPath(); c.arc(x, y, 1.8, 0, Math.PI * 2); c.fill(); return true; }
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
|
|
700
866
|
const GLYPH: Partial<Record<Constraint['type'], string>> = { h: 'H', v: 'V', par: '∥', perp: '⊥', col: '—', tan: 'T', eq: '=', eqr: '=', mid: 'M', online: '∘', oncircle: '∘', conc: '◎' };
|
|
701
867
|
|
|
702
868
|
function badgeSpots(T: Xform, sk: Sketch, c: Constraint): [number, number][] {
|
|
@@ -712,7 +878,7 @@ function badgeSpots(T: Xform, sk: Sketch, c: Constraint): [number, number][] {
|
|
|
712
878
|
out.push([x1 + (x2 - x1) * frac + nx * off * side, y1 + (y2 - y1) * frac + ny * off * side]);
|
|
713
879
|
};
|
|
714
880
|
if (c.type === 'h' || c.type === 'v') {
|
|
715
|
-
const lid =
|
|
881
|
+
const lid = lineBetween(sk, c.a!, c.b!);
|
|
716
882
|
if (lid) onLine(lid, 0.22, -1);
|
|
717
883
|
else {
|
|
718
884
|
const a = P(c.a!), b = P(c.b!);
|
|
@@ -755,7 +921,7 @@ function drawBadges(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
755
921
|
}
|
|
756
922
|
for (const id in sk.cons) {
|
|
757
923
|
const k = sk.cons[id];
|
|
758
|
-
if (isDim(k)) continue;
|
|
924
|
+
if (isDim(k) || k.elev) continue;
|
|
759
925
|
const g = GLYPH[k.type];
|
|
760
926
|
if (!g) continue;
|
|
761
927
|
const related = selIds.has(id) || [k.l, k.l1, k.l2, k.c, k.c1, k.c2].some(x => x && selIds.has(x)) || [k.a, k.b, k.p].some(x => x && selPts.has(x));
|
|
@@ -767,8 +933,9 @@ function drawBadges(c: C2D, T: Xform, sk: Sketch): void {
|
|
|
767
933
|
rr(c, x - 9, y - 9, 18, 18, 5);
|
|
768
934
|
c.fillStyle = bad ? TH.red : selected ? TH.yellow : TH.badge; c.fill();
|
|
769
935
|
c.lineWidth = 1; c.strokeStyle = bad ? TH.red : TH.badgeline; c.stroke();
|
|
770
|
-
|
|
771
|
-
c.
|
|
936
|
+
const ink = bad ? '#fff' : selected ? '#181A1C' : TH.badgefg;
|
|
937
|
+
c.fillStyle = ink;
|
|
938
|
+
if (!drawSymbol(c, g, x, y, ink)) { c.font = '700 11px ' + FONT; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(g, x, y + 0.5); }
|
|
772
939
|
c.restore();
|
|
773
940
|
addHit({ kind: 'cons', id, x: x - 10, y: y - 10, w: 20, h: 20 });
|
|
774
941
|
}
|
|
@@ -792,74 +959,80 @@ export function cloudPath(c: C2D, x: number, y: number, w: number, h: number): v
|
|
|
792
959
|
function drawNotes(c: C2D, T: Xform, sk: Sketch): void {
|
|
793
960
|
if (!settings.layers.annot) return;
|
|
794
961
|
const dim = ed.mode === 'draft' ? 0.75 : 1;
|
|
962
|
+
nextThumbFrame();
|
|
795
963
|
for (const n of sk.notes) {
|
|
796
964
|
const selected = isSel('note', n.id);
|
|
965
|
+
if (isLink(n)) { drawLink(c, T, n, { selected, alpha: dim, info: linkInfoOf(n.link) }); continue; }
|
|
797
966
|
c.save();
|
|
798
967
|
c.globalAlpha = dim; c.strokeStyle = n.color; c.fillStyle = n.color; c.lineCap = 'round'; c.lineJoin = 'round';
|
|
799
968
|
if (selected) { c.shadowColor = TH.yellow; c.shadowBlur = 0; }
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const p1 = n.pts[n.pts.length - 2], p2 = n.pts[n.pts.length - 1];
|
|
807
|
-
const [x1, y1] = S(T, p1[0], p1[1]), [x2, y2] = S(T, p2[0], p2[1]);
|
|
808
|
-
const a = Math.atan2(y2 - y1, x2 - x1);
|
|
809
|
-
c.beginPath(); c.moveTo(x2, y2);
|
|
810
|
-
c.lineTo(x2 - 13 * Math.cos(a - 0.45), y2 - 13 * Math.sin(a - 0.45));
|
|
811
|
-
c.lineTo(x2 - 13 * Math.cos(a + 0.45), y2 - 13 * Math.sin(a + 0.45));
|
|
812
|
-
c.closePath(); c.fill();
|
|
813
|
-
}
|
|
969
|
+
const look = noteLook(n);
|
|
970
|
+
const outline = shapeOutline(n);
|
|
971
|
+
if (outline) {
|
|
972
|
+
strokeOutline(c, outline.map(p => S(T, p[0], p[1])), look, selected ? 'rgba(255,173,0,.45)' : undefined);
|
|
973
|
+
} else if ((n.type === 'pen' || isSegment(n)) && n.pts) {
|
|
974
|
+
strokeMarkup(c, n.pts.map(p => S(T, p[0], p[1])), look, selected ? 'rgba(255,173,0,.45)' : undefined);
|
|
814
975
|
} else if (n.type === 'text') {
|
|
815
|
-
|
|
816
|
-
c
|
|
817
|
-
|
|
818
|
-
rr(c,
|
|
976
|
+
// laid out at the one text size and scaled to the note's size on the drawing (its outline stays the same width)
|
|
977
|
+
const { x, w, mid, k } = noteTextBox(T, n, c), lw = selected ? 2.5 : 1.5;
|
|
978
|
+
c.translate(x, mid); c.scale(k, k);
|
|
979
|
+
rr(c, 0, -14, w / k, 28, 7);
|
|
819
980
|
c.fillStyle = TH.bg; c.globalAlpha = 0.92 * dim; c.fill(); c.globalAlpha = dim;
|
|
820
|
-
c.lineWidth =
|
|
821
|
-
c.
|
|
981
|
+
c.lineWidth = lw / k; c.strokeStyle = selected ? TH.yellow : n.color;
|
|
982
|
+
c.setLineDash(dashFor(look.style, lw).map(v => v / k)); c.stroke(); c.setLineDash([]);
|
|
983
|
+
c.font = noteFont(NOTE_TEXT_SIZE);
|
|
984
|
+
c.fillStyle = n.color; c.textAlign = 'left'; c.textBaseline = 'middle'; c.fillText(n.text!, 8, 0.5 / k); // (left: not whatever was drawn last)
|
|
822
985
|
} else if (n.type === 'cloud' && n.pts) {
|
|
823
986
|
const [x1, y1] = S(T, n.pts[0][0], n.pts[0][1]), [x2, y2] = S(T, n.pts[1][0], n.pts[1][1]);
|
|
824
987
|
cloudPath(c, Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
|
|
825
|
-
c.lineWidth =
|
|
826
|
-
|
|
988
|
+
if (selected) { c.save(); c.lineWidth = look.width + 6; c.strokeStyle = 'rgba(255,173,0,.45)'; c.stroke(); c.restore(); }
|
|
989
|
+
c.lineWidth = look.width;
|
|
990
|
+
if (look.style === 'dot') c.lineCap = 'round';
|
|
991
|
+
c.setLineDash(dashFor(look.style, look.width));
|
|
827
992
|
c.stroke();
|
|
993
|
+
c.setLineDash([]);
|
|
828
994
|
}
|
|
829
995
|
c.restore();
|
|
830
996
|
}
|
|
831
997
|
}
|
|
832
998
|
|
|
833
|
-
/** Selected
|
|
999
|
+
/** Selected images: blue outline with corner (scale) and side (stretch) handles — one image alone — or a lock badge when locked. */
|
|
834
1000
|
function drawUnderlaySelection(c: C2D, T: Xform): void {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1001
|
+
if (!settings.layers.underlay || ed.mode !== 'annotate') return;
|
|
1002
|
+
const sel = page().underlays.filter(u => isSel('underlay', u.id));
|
|
1003
|
+
for (const u of sel) {
|
|
1004
|
+
const hs = underlayHandles(T, u), nw = hs[0];
|
|
1005
|
+
c.save();
|
|
1006
|
+
c.strokeStyle = TH.blue; c.lineWidth = 1.5;
|
|
1007
|
+
if (u.locked) c.setLineDash([6, 4]);
|
|
1008
|
+
// its outline, turned with it: through the corners
|
|
1009
|
+
c.beginPath(); [hs[0], hs[2], hs[4], hs[6]].forEach((q, i) => (i ? c.lineTo(q.x, q.y) : c.moveTo(q.x, q.y))); c.closePath(); c.stroke();
|
|
1010
|
+
c.setLineDash([]);
|
|
1011
|
+
if (u.locked) drawLock(c, nw.x + 12, nw.y + 12);
|
|
1012
|
+
else if (sel.length === 1) drawHandleSet(c, hs);
|
|
1013
|
+
c.restore();
|
|
1014
|
+
}
|
|
846
1015
|
}
|
|
847
1016
|
|
|
848
|
-
/** Resize handles: round white corners (and arrow ends), blue bars on the sides. */
|
|
849
|
-
function drawHandleSet(c: C2D, hs: { part: string; x: number; y: number }[]): void {
|
|
1017
|
+
/** Resize handles: round white corners (and arrow ends), blue bars on the sides — along them, on a turned image (`ang`). */
|
|
1018
|
+
function drawHandleSet(c: C2D, hs: { part: string; x: number; y: number; ang?: number }[]): void {
|
|
850
1019
|
const r = ed.lastPointerTouch ? 8 : 6;
|
|
851
1020
|
for (const hd of hs) {
|
|
852
1021
|
const side = hd.part === 'n' || hd.part === 's' || hd.part === 'e' || hd.part === 'w';
|
|
1022
|
+
c.save();
|
|
1023
|
+
c.translate(hd.x, hd.y);
|
|
1024
|
+
if (hd.ang) c.rotate(hd.ang);
|
|
853
1025
|
c.beginPath();
|
|
854
|
-
if (!side) c.arc(
|
|
855
|
-
else if (hd.part === 'e' || hd.part === 'w') rr(c,
|
|
856
|
-
else rr(c,
|
|
1026
|
+
if (!side) c.arc(0, 0, r, 0, Math.PI * 2);
|
|
1027
|
+
else if (hd.part === 'e' || hd.part === 'w') rr(c, -3.5, -15, 7, 30, 3.5);
|
|
1028
|
+
else rr(c, -12, -3, 24, 6, 3);
|
|
857
1029
|
c.fillStyle = side ? TH.blue : '#fff'; c.fill();
|
|
858
1030
|
c.lineWidth = 1.5; c.strokeStyle = TH.blue; c.stroke();
|
|
1031
|
+
c.restore();
|
|
859
1032
|
}
|
|
860
1033
|
}
|
|
861
1034
|
|
|
862
|
-
/** A single selected
|
|
1035
|
+
/** A single selected piece of markup: its adjustment handles (and a box around all but the straight ones and notes). */
|
|
863
1036
|
function drawNoteSelection(c: C2D, T: Xform): void {
|
|
864
1037
|
if (ed.mode !== 'annotate' || !settings.layers.annot) return;
|
|
865
1038
|
const notes = ed.sel.filter(s => s.type === 'note');
|
|
@@ -869,7 +1042,8 @@ function drawNoteSelection(c: C2D, T: Xform): void {
|
|
|
869
1042
|
const hs = noteHandles(T, n);
|
|
870
1043
|
if (!hs.length) return;
|
|
871
1044
|
c.save();
|
|
872
|
-
|
|
1045
|
+
// (a note has its own outline, and a link its card)
|
|
1046
|
+
if (!isSegment(n) && n.type !== 'text' && n.type !== 'link') {
|
|
873
1047
|
const nw = hs[0], se = hs[4];
|
|
874
1048
|
c.strokeStyle = TH.blue; c.lineWidth = 1.2; c.setLineDash([5, 4]);
|
|
875
1049
|
c.strokeRect(nw.x, nw.y, se.x - nw.x, se.y - nw.y);
|
|
@@ -899,6 +1073,10 @@ function drawSnap(c: C2D, T: Xform): void {
|
|
|
899
1073
|
else if (s.kind === 'inter') { c.moveTo(x - 6, y - 6); c.lineTo(x + 6, y + 6); c.moveTo(x + 6, y - 6); c.lineTo(x - 6, y + 6); }
|
|
900
1074
|
else c.rect(x - 5, y - 5, 10, 10);
|
|
901
1075
|
c.stroke();
|
|
1076
|
+
} else if (s.kind === 'grid') {
|
|
1077
|
+
// on the grid: the grid point it's on, marked (it moves in steps from one to the next)
|
|
1078
|
+
c.beginPath(); c.arc(x, y, 3.5, 0, Math.PI * 2); c.fill();
|
|
1079
|
+
c.lineWidth = 1.2; c.beginPath(); c.arc(x, y, 7, 0, Math.PI * 2); c.stroke();
|
|
902
1080
|
}
|
|
903
1081
|
const lbl = s.label || (s.guides && s.guides.length ? s.guides.map(g => g.label).join(' · ') : '');
|
|
904
1082
|
if (lbl) {
|
|
@@ -913,8 +1091,9 @@ function drawSnap(c: C2D, T: Xform): void {
|
|
|
913
1091
|
|
|
914
1092
|
/** Paint the browser canvas, keeping the frame's tap targets for hit testing. */
|
|
915
1093
|
export function draw(): void {
|
|
916
|
-
const { W, H, dpr, ctx } = cvs;
|
|
917
|
-
|
|
1094
|
+
const { cv, W, H, dpr, ctx } = cvs;
|
|
1095
|
+
// (no browser canvas — a unit test — nothing to paint)
|
|
1096
|
+
if (!W || !cv) return;
|
|
918
1097
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
919
1098
|
ed.hitChips = drawPlan(ctx, W, H);
|
|
920
1099
|
}
|
|
@@ -926,25 +1105,106 @@ export function draw(): void {
|
|
|
926
1105
|
export function drawPlan(ctx: C2D, W: number, H: number): HitChip[] {
|
|
927
1106
|
const out: HitChip[] = [];
|
|
928
1107
|
hits = out;
|
|
929
|
-
try { paintPlan(ctx, W, H); } finally { hits = null; }
|
|
1108
|
+
try { paintPlan(ctx, W, H); paintOverlay(ctx, W, H); } finally { hits = null; }
|
|
930
1109
|
return out;
|
|
931
1110
|
}
|
|
932
1111
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1112
|
+
/**
|
|
1113
|
+
* How a host that keeps the drawing as a picture (see PlanCanvas.native) wants it drawn. It moves and scales the
|
|
1114
|
+
* picture with the view until it's redrawn, so: `margin` px of background and grid past each edge of the screen (so
|
|
1115
|
+
* a picture zoomed out or panned a little shows no edge), and what stays put on the screen however the view moves —
|
|
1116
|
+
* the wall view's key plan, the conflict border — drawn on `fixed` instead. Without them, everything is drawn on the
|
|
1117
|
+
* one canvas, to the screen's edges (the browser).
|
|
1118
|
+
*/
|
|
1119
|
+
/**
|
|
1120
|
+
* `layer`: just the background, grid and photos and plans ('under'), or everything over them ('above') — a host that
|
|
1121
|
+
* keeps them apart redraws the photos alone as one is turned or moved. Without it, all of it.
|
|
1122
|
+
*/
|
|
1123
|
+
export interface KeepOpts { margin?: number; fixed?: C2D; layer?: 'under' | 'above' }
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* The drawing without its moving parts (see drawPlanOverlay), for a host that keeps it as a picture and redraws only
|
|
1127
|
+
* those parts as the pointer moves. Returns the tap targets, as drawPlan does.
|
|
1128
|
+
*/
|
|
1129
|
+
export function drawPlanStatic(ctx: C2D, W: number, H: number, opts: KeepOpts = {}): HitChip[] {
|
|
1130
|
+
const out: HitChip[] = [];
|
|
1131
|
+
hits = out;
|
|
1132
|
+
try { paintPlan(ctx, W, H, opts); } finally { hits = null; }
|
|
1133
|
+
return out;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** The photos and plans under the drawing, with the background and grid (see KeepOpts.layer). */
|
|
1137
|
+
export function drawPlanUnder(ctx: C2D, W: number, H: number, opts: KeepOpts = {}): void {
|
|
1138
|
+
paintPlan(ctx, W, H, { ...opts, layer: 'under' });
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** The parts that move with the pointer: a tool's preview, the snap marker, the selection box, the conflict tint. */
|
|
1142
|
+
export function drawPlanOverlay(ctx: C2D, W: number, H: number, opts: KeepOpts = {}): void {
|
|
1143
|
+
paintOverlay(ctx, W, H, opts.fixed ?? ctx);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function paintPlan(ctx: C2D, W: number, H: number, opts: KeepOpts = {}): void {
|
|
1147
|
+
const m = opts.margin ?? 0, wall = elevOpen() && !!elev.frame;
|
|
1148
|
+
if (opts.layer !== 'above') { ctx.fillStyle = TH.bg; ctx.fillRect(-m, -m, W + 2 * m, H + 2 * m); }
|
|
1149
|
+
// the photos and plans under the drawing (the wall view has none: its grid is on the wall, drawn with it)
|
|
1150
|
+
if (opts.layer === 'under') { if (!wall) { const T = mainT(); drawGrid(ctx, T, m); drawUnderlay(ctx, T); } return; }
|
|
1151
|
+
const sk = SK();
|
|
1152
|
+
// (the sketch can't change while a frame is drawn: index its lines once for the frame)
|
|
1153
|
+
withLineIndex(sk, () => {
|
|
1154
|
+
if (wall) paintWall(ctx, W, opts.fixed ?? ctx);
|
|
1155
|
+
else paintPlanView(ctx, sk, m, opts.layer !== 'above');
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function paintPlanView(ctx: C2D, sk: Sketch, m: number, under = true): void {
|
|
1160
|
+
const T = mainT();
|
|
1161
|
+
if (under) { drawGrid(ctx, T, m); drawUnderlay(ctx, T); }
|
|
937
1162
|
if (settings.layers.rooms) drawRooms(ctx, T, sk);
|
|
1163
|
+
if (settings.layers.cabinets) drawCabinetFootprints(ctx, T, sk);
|
|
938
1164
|
drawWalls(ctx, T, sk); drawOpenings(ctx, T, sk); drawLines(ctx, T, sk); drawCurves(ctx, T, sk);
|
|
939
1165
|
drawPrintDims(ctx, T, sk);
|
|
940
1166
|
if (ed.mode === 'draft') drawBadges(ctx, T, sk);
|
|
941
|
-
|
|
1167
|
+
// (the drawing's points only in Draft: Annotate can't move them, and shouldn't look as if it could — the user's
|
|
1168
|
+
// call, 2026-10-21)
|
|
1169
|
+
if (ed.mode === 'draft') drawPoints(ctx, T, sk);
|
|
942
1170
|
drawOpeningAnchors(ctx, T, sk);
|
|
943
1171
|
drawDims(ctx, T, sk);
|
|
944
1172
|
drawOpeningDims(ctx, T, sk);
|
|
945
1173
|
drawNotes(ctx, T, sk);
|
|
946
|
-
drawUnderlaySelection(ctx, T);
|
|
947
1174
|
drawNoteSelection(ctx, T);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* The wall view: the open wall face, drawn as the plan is (its grid on the wall only, lines, curves, constraints, dimensions, points), over
|
|
1179
|
+
* the wall itself (render/elevation.ts) and under the dimensions its doors, windows and cabinets get, plus a key plan.
|
|
1180
|
+
*/
|
|
1181
|
+
function paintWall(ctx: C2D, W: number, fixed: C2D): void {
|
|
1182
|
+
const sk = SK() as WallFace, plan = planSK(), f = elev.frame!, T = mainT();
|
|
1183
|
+
// the grid only on the wall itself: outside it is plain, so it's plain that nothing goes there
|
|
1184
|
+
const [gx0, gy0] = S(T, f.face[0], f.H), [gx1, gy1] = S(T, f.face[1], 0);
|
|
1185
|
+
ctx.save();
|
|
1186
|
+
ctx.beginPath(); ctx.rect(gx0, gy0, gx1 - gx0, gy1 - gy0); ctx.clip();
|
|
1187
|
+
drawGrid(ctx, T);
|
|
1188
|
+
ctx.restore();
|
|
1189
|
+
drawWallBackdrop(ctx, T, plan, f, sk);
|
|
1190
|
+
drawLines(ctx, T, sk); drawCurves(ctx, T, sk);
|
|
1191
|
+
// (over the edges — its own and its neighbours' — so a selected cabinet's outline shows all the way round)
|
|
1192
|
+
drawWallSelection(ctx, T, plan, f, sk);
|
|
1193
|
+
drawPrintDims(ctx, T, sk);
|
|
1194
|
+
drawBadges(ctx, T, sk);
|
|
1195
|
+
if (ed.mode === 'draft') drawPoints(ctx, T, sk);
|
|
1196
|
+
drawDims(ctx, T, sk);
|
|
1197
|
+
const on = (a: ElevAct) => isSel('elev', JSON.stringify(a)) || (a.k === 'fdim' && isSel('dim', a.id));
|
|
1198
|
+
for (const h of drawWallChips(ctx, T, plan, f, sk, on)) addHit({ kind: 'elev', id: JSON.stringify(h.act), part: h.look, x: h.x, y: h.y, w: h.w, h: h.h });
|
|
1199
|
+
// (where you stand: it stays put on the screen as the wall is panned and zoomed)
|
|
1200
|
+
const k = keyPlanSize(W);
|
|
1201
|
+
drawKeyPlan(fixed, W - k - 14, 70, k, plan, f);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function paintOverlay(ctx: C2D, W: number, H: number, fixed: C2D = ctx): void {
|
|
1205
|
+
const T = mainT();
|
|
1206
|
+
// a selected image's outline and handles: with the moving parts, as it's turned, moved or sized (over the drawing)
|
|
1207
|
+
drawUnderlaySelection(ctx, T);
|
|
948
1208
|
if (ed.preview) ed.preview(ctx, T);
|
|
949
1209
|
drawSnap(ctx, T);
|
|
950
1210
|
if (ed.boxSel) {
|
|
@@ -958,11 +1218,12 @@ function paintPlan(ctx: C2D, W: number, H: number): void {
|
|
|
958
1218
|
ctx.strokeRect(Math.min(x0, x1) + 0.5, Math.min(y0, y1) + 0.5, Math.abs(x1 - x0), Math.abs(y1 - y0));
|
|
959
1219
|
ctx.restore();
|
|
960
1220
|
}
|
|
1221
|
+
// (the screen's edge, however the view moves)
|
|
961
1222
|
if (ed.conflictTint) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1223
|
+
fixed.save();
|
|
1224
|
+
fixed.fillStyle = 'rgba(218,17,0,.07)'; fixed.fillRect(0, 0, W, H);
|
|
1225
|
+
fixed.strokeStyle = 'rgba(218,17,0,.7)'; fixed.lineWidth = 6; fixed.strokeRect(3, 3, W - 6, H - 6);
|
|
1226
|
+
fixed.restore();
|
|
966
1227
|
}
|
|
967
1228
|
}
|
|
968
1229
|
|
|
@@ -991,6 +1252,7 @@ export function paintThumb(c: C2D, w: number, h: number, pg: Page): void {
|
|
|
991
1252
|
const save = { ana: ed.ana, sel: ed.sel, hover: ed.hover, W: cvs.W, H: cvs.H };
|
|
992
1253
|
ed.ana = { dof: 0, det: new Set(), detR: new Set(), bad: new Set(), ok: true };
|
|
993
1254
|
ed.sel = []; ed.hover = null; cvs.W = w; cvs.H = h;
|
|
994
|
-
|
|
1255
|
+
// (the page's lines indexed by their ends while it's drawn, as a frame is: each room's edges looked up, not searched for)
|
|
1256
|
+
try { withLineIndex(sk, () => { drawRooms(c, T, sk, true); drawWalls(c, T, sk, true); drawLines(c, T, sk, true); drawCurves(c, T, sk, true); }); }
|
|
995
1257
|
finally { cvs.W = save.W; cvs.H = save.H; ed.ana = save.ana; ed.sel = save.sel; ed.hover = save.hover; }
|
|
996
1258
|
}
|