@design-core/core 0.0.6 → 50.1.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/.eslintrc.json +4 -0
- package/.github/workflows/DesignCoreQA.yml +3 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +12 -11
- package/core/dimensions/angularDimension.js +38 -37
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
- package/core/dimensions/diametricDimension.js +14 -13
- package/core/dimensions/dimension.js +62 -55
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
- package/core/dimensions/radialDimension.js +12 -11
- package/core/dimensions/rotatedDimension.js +20 -19
- package/core/entities/arc.js +123 -156
- package/core/entities/arctext.js +222 -184
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +346 -273
- package/core/entities/insert.js +68 -83
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +7 -7
- package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +190 -201
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/boundingBox.js +11 -11
- package/core/lib/canvas.js +232 -168
- package/core/lib/clipboard.js +9 -3
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +15 -14
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +33 -26
- package/core/lib/inputManager.js +36 -12
- package/core/lib/intersect.js +25 -0
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +9 -2
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +28 -6
- package/core/lib/selectionManager.js +20 -20
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +26 -7
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +60 -44
- package/core/tables/block.js +50 -81
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +117 -143
- package/core/tools/chamferFilletBase.js +138 -37
- package/core/tools/copy.js +11 -11
- package/core/tools/copybase.js +3 -3
- package/core/tools/copyclip.js +3 -3
- package/core/tools/cornerEntity.js +2 -2
- package/core/tools/cutclip.js +3 -3
- package/core/tools/explode.js +7 -7
- package/core/tools/extend.js +138 -25
- package/core/tools/fillet.js +92 -189
- package/core/tools/matchProp.js +3 -12
- package/core/tools/mirror.js +15 -14
- package/core/tools/move.js +8 -8
- package/core/tools/offset.js +31 -33
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +19 -16
- package/core/tools/scale.js +7 -7
- package/core/tools/trim.js +271 -29
- package/core/tools/zoom.js +1 -1
- package/cspell.json +321 -0
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +17 -10
- package/test/dimensions/angularDimension.test.js +13 -13
- package/test/dimensions/diametricDimension.test.js +15 -15
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
- package/test/dimensions/linearDimensionBase.test.js +62 -0
- package/test/dimensions/radialDimension.test.js +15 -15
- package/test/dimensions/rotatedDimension.test.js +18 -11
- package/test/entities/arc.test.js +83 -86
- package/test/entities/arctext.test.js +184 -57
- package/test/entities/circle.test.js +68 -96
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +569 -96
- package/test/entities/insert.test.js +99 -4
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/point.test.js +3 -3
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +302 -77
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +5 -5
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +744 -125
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +81 -20
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +72 -130
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +25 -25
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +92 -13
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +158 -6
- package/test/tools/chamfer.test.js +273 -51
- package/test/tools/chamferFilletBase.test.js +125 -36
- package/test/tools/copy.test.js +8 -8
- package/test/tools/copybase.test.js +2 -2
- package/test/tools/copyclip.test.js +2 -2
- package/test/tools/cornerEntity.test.js +8 -8
- package/test/tools/cutclip.test.js +2 -2
- package/test/tools/erase.test.js +6 -6
- package/test/tools/explode.test.js +5 -5
- package/test/tools/extend.test.js +373 -42
- package/test/tools/fillet.test.js +230 -49
- package/test/tools/matchProp.test.js +11 -11
- package/test/tools/mirror.test.js +39 -39
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +222 -55
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +39 -9
- package/test/tools/scale.test.js +33 -33
- package/test/tools/trim.test.js +579 -53
- package/test/tools/zoom.test.js +12 -12
- package/core/lib/selectionWindow.js +0 -83
package/core/lib/snapping.js
CHANGED
|
@@ -1,198 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import { Point } from '../entities/point.js';
|
|
3
|
-
import { Colours } from './colours.js';
|
|
4
3
|
import { Utils } from './utils.js';
|
|
5
4
|
|
|
5
|
+
import { SnapPoint } from './auxiliary/snapPoint.js';
|
|
6
|
+
import { TrackingLine } from './auxiliary/trackingLine.js';
|
|
6
7
|
import { DesignCore } from '../designCore.js';
|
|
7
|
-
|
|
8
|
-
/** SnapPoint Class */
|
|
9
|
-
export class SnapPoint {
|
|
10
|
-
static Type = Object.freeze({
|
|
11
|
-
NONE: 'none',
|
|
12
|
-
END: 'end',
|
|
13
|
-
MID: 'mid',
|
|
14
|
-
CENTRE: 'centre',
|
|
15
|
-
QUADRANT: 'quadrant',
|
|
16
|
-
NEAREST: 'nearest',
|
|
17
|
-
TANGENT: 'tangent',
|
|
18
|
-
NODE: 'node',
|
|
19
|
-
PERPENDICULAR: 'perpendicular',
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Create SnapPoint
|
|
24
|
-
* @param {Point} snapPoint
|
|
25
|
-
* @param {string} type - snap type from SnapPoint.Type
|
|
26
|
-
*/
|
|
27
|
-
constructor(snapPoint, type = SnapPoint.Type.END) {
|
|
28
|
-
this.snapPoint = snapPoint;
|
|
29
|
-
this.type = type;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Draw the entity
|
|
34
|
-
* @param {Object} ctx - context
|
|
35
|
-
* @param {number} scale
|
|
36
|
-
*/
|
|
37
|
-
draw(ctx, scale) {
|
|
38
|
-
const snapColour = DesignCore.Settings.snaptrackingcolour;
|
|
39
|
-
const size = 6 / scale;
|
|
40
|
-
const x = this.snapPoint.x;
|
|
41
|
-
const y = this.snapPoint.y;
|
|
42
|
-
const lineWidth = 2.5 / scale;
|
|
43
|
-
|
|
44
|
-
try { // HTML Canvas
|
|
45
|
-
ctx.strokeStyle = Colours.rgbToString(snapColour);
|
|
46
|
-
ctx.lineWidth = lineWidth;
|
|
47
|
-
ctx.setLineDash([]);
|
|
48
|
-
ctx.beginPath();
|
|
49
|
-
} catch { // Cairo
|
|
50
|
-
const rgbColour = Colours.rgbToScaledRGB(snapColour);
|
|
51
|
-
ctx.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
|
|
52
|
-
ctx.setLineWidth(lineWidth);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
switch (this.type) {
|
|
56
|
-
case SnapPoint.Type.END: // square
|
|
57
|
-
ctx.moveTo(x - size, y - size);
|
|
58
|
-
ctx.lineTo(x + size, y - size);
|
|
59
|
-
ctx.lineTo(x + size, y + size);
|
|
60
|
-
ctx.lineTo(x - size, y + size);
|
|
61
|
-
ctx.closePath();
|
|
62
|
-
break;
|
|
63
|
-
case SnapPoint.Type.MID: // triangle
|
|
64
|
-
ctx.moveTo(x, y + size);
|
|
65
|
-
ctx.lineTo(x - size, y - size);
|
|
66
|
-
ctx.lineTo(x + size, y - size);
|
|
67
|
-
ctx.closePath();
|
|
68
|
-
break;
|
|
69
|
-
case SnapPoint.Type.QUADRANT: // diamond
|
|
70
|
-
ctx.moveTo(x, y - size);
|
|
71
|
-
ctx.lineTo(x + size, y);
|
|
72
|
-
ctx.lineTo(x, y + size);
|
|
73
|
-
ctx.lineTo(x - size, y);
|
|
74
|
-
ctx.closePath();
|
|
75
|
-
break;
|
|
76
|
-
case SnapPoint.Type.NEAREST: // hourglass (X with horizontal lines at top and bottom)
|
|
77
|
-
ctx.moveTo(x - size, y - size);
|
|
78
|
-
ctx.lineTo(x + size, y - size);
|
|
79
|
-
ctx.lineTo(x - size, y + size);
|
|
80
|
-
ctx.lineTo(x + size, y + size);
|
|
81
|
-
ctx.closePath();
|
|
82
|
-
break;
|
|
83
|
-
case SnapPoint.Type.TANGENT: // circle with a horizontal line over the top
|
|
84
|
-
ctx.arc(x, y, size, 0, 6.283);
|
|
85
|
-
ctx.moveTo(x - size, y + size * 1.5);
|
|
86
|
-
ctx.lineTo(x + size, y + size * 1.5);
|
|
87
|
-
break;
|
|
88
|
-
case SnapPoint.Type.NODE: // circle with an X inside
|
|
89
|
-
ctx.arc(x, y, size, 0, 6.283);
|
|
90
|
-
ctx.moveTo(x - size * 0.7, y - size * 0.7);
|
|
91
|
-
ctx.lineTo(x + size * 0.7, y + size * 0.7);
|
|
92
|
-
ctx.moveTo(x + size * 0.7, y - size * 0.7);
|
|
93
|
-
ctx.lineTo(x - size * 0.7, y + size * 0.7);
|
|
94
|
-
break;
|
|
95
|
-
case SnapPoint.Type.PERPENDICULAR: // right-angle corner (L-shape)
|
|
96
|
-
ctx.moveTo(x - size, y + size);
|
|
97
|
-
ctx.lineTo(x - size, y - size);
|
|
98
|
-
ctx.lineTo(x + size, y - size);
|
|
99
|
-
break;
|
|
100
|
-
default: // CENTRE - circle
|
|
101
|
-
ctx.arc(x, y, size, 0, 6.283);
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
ctx.stroke();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** TrackingLine Class - dotted tracking line for polar/ortho snap */
|
|
110
|
-
class TrackingLine {
|
|
111
|
-
/**
|
|
112
|
-
* Create TrackingLine
|
|
113
|
-
* @param {Point} inputPoint - the previous input point (line passes through this)
|
|
114
|
-
* @param {Point} snapPoint - the current snapped mouse point (direction reference)
|
|
115
|
-
*/
|
|
116
|
-
constructor(inputPoint, snapPoint) {
|
|
117
|
-
this.inputPoint = inputPoint;
|
|
118
|
-
this.snapPoint = snapPoint;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Draw the tracking line, extended to the visible canvas edges
|
|
123
|
-
* @param {Object} ctx - context
|
|
124
|
-
* @param {number} scale
|
|
125
|
-
*/
|
|
126
|
-
draw(ctx, scale) {
|
|
127
|
-
const from = this.inputPoint;
|
|
128
|
-
const dir = this.snapPoint.subtract(from);
|
|
129
|
-
const lineWidth = 2 / scale;
|
|
130
|
-
|
|
131
|
-
if (dir.x === 0 && dir.y === 0) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Compute visible canvas bounds in scene space
|
|
136
|
-
const w = DesignCore.Canvas.width;
|
|
137
|
-
const h = DesignCore.Canvas.height;
|
|
138
|
-
const corner0 = DesignCore.Mouse.transformToScene(new Point(0, 0));
|
|
139
|
-
const corner1 = DesignCore.Mouse.transformToScene(new Point(w, h));
|
|
140
|
-
const boundsMin = corner0.min(corner1);
|
|
141
|
-
const boundsMax = corner0.max(corner1);
|
|
142
|
-
|
|
143
|
-
// Clip the infinite line (through 'from' in direction dir) to canvas bounds
|
|
144
|
-
let tMin = -Infinity;
|
|
145
|
-
let tMax = Infinity;
|
|
146
|
-
|
|
147
|
-
if (dir.x !== 0) {
|
|
148
|
-
const tx1 = (boundsMin.x - from.x) / dir.x;
|
|
149
|
-
const tx2 = (boundsMax.x - from.x) / dir.x;
|
|
150
|
-
tMin = Math.max(tMin, Math.min(tx1, tx2));
|
|
151
|
-
tMax = Math.min(tMax, Math.max(tx1, tx2));
|
|
152
|
-
} else if (from.x < boundsMin.x || from.x > boundsMax.x) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (dir.y !== 0) {
|
|
157
|
-
const ty1 = (boundsMin.y - from.y) / dir.y;
|
|
158
|
-
const ty2 = (boundsMax.y - from.y) / dir.y;
|
|
159
|
-
tMin = Math.max(tMin, Math.min(ty1, ty2));
|
|
160
|
-
tMax = Math.min(tMax, Math.max(ty1, ty2));
|
|
161
|
-
} else if (from.y < boundsMin.y || from.y > boundsMax.y) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (tMax < tMin) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const start = from.add(dir.scale(tMin));
|
|
170
|
-
const end = from.add(dir.scale(tMax));
|
|
171
|
-
|
|
172
|
-
const lineColour = DesignCore.Settings.snaptrackingcolour;
|
|
173
|
-
const dashSize = 4 / scale;
|
|
174
|
-
|
|
175
|
-
ctx.save();
|
|
176
|
-
|
|
177
|
-
try { // HTML Canvas
|
|
178
|
-
ctx.beginPath();
|
|
179
|
-
ctx.strokeStyle = Colours.rgbToString(lineColour);
|
|
180
|
-
ctx.lineWidth = lineWidth;
|
|
181
|
-
ctx.setLineDash([dashSize, dashSize]);
|
|
182
|
-
} catch { // Cairo
|
|
183
|
-
const rgbColour = Colours.rgbToScaledRGB(lineColour);
|
|
184
|
-
ctx.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
|
|
185
|
-
ctx.setLineWidth(lineWidth);
|
|
186
|
-
ctx.setDash([dashSize, dashSize], 0);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
ctx.moveTo(start.x, start.y);
|
|
190
|
-
ctx.lineTo(end.x, end.y);
|
|
191
|
-
ctx.stroke();
|
|
192
|
-
|
|
193
|
-
ctx.restore();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
8
|
+
import { Property } from '../properties/property.js';
|
|
196
9
|
|
|
197
10
|
/** Snapping Class */
|
|
198
11
|
export class Snapping {
|
|
@@ -271,7 +84,7 @@ export class Snapping {
|
|
|
271
84
|
let delta = snapAperture / DesignCore.Canvas.getScale(); // convert to scene space
|
|
272
85
|
|
|
273
86
|
for (let i = 0; i < DesignCore.Scene.entities.count(); i++) {
|
|
274
|
-
const layer = DesignCore.LayerManager.getItemByName(DesignCore.Scene.entities.get(i).
|
|
87
|
+
const layer = DesignCore.LayerManager.getItemByName(DesignCore.Scene.entities.get(i).getProperty(Property.Names.LAYER));
|
|
275
88
|
|
|
276
89
|
if (!layer?.isVisible) {
|
|
277
90
|
continue;
|
package/core/lib/stateManager.js
CHANGED
|
@@ -200,7 +200,7 @@ class State {
|
|
|
200
200
|
// capture previous properties for undo
|
|
201
201
|
const previousProperties = {};
|
|
202
202
|
for (const prop of Object.getOwnPropertyNames(stateChange.properties)) {
|
|
203
|
-
previousProperties[prop] = stateChange.entity
|
|
203
|
+
previousProperties[prop] = stateChange.entity.getProperty(prop);
|
|
204
204
|
}
|
|
205
205
|
const undoStateChange = new UpdateState(stateChange.entity, previousProperties);
|
|
206
206
|
this.undoStateChanges.push(undoStateChange);
|
|
@@ -208,6 +208,15 @@ class State {
|
|
|
208
208
|
this.entityManager.update(index, stateChange.properties);
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
+
|
|
212
|
+
if (stateChange instanceof ReplaceState) {
|
|
213
|
+
const index = this.entityManager.indexOf(stateChange.entity);
|
|
214
|
+
if (index !== -1) {
|
|
215
|
+
this.entityManager.replace(index, stateChange.replacement);
|
|
216
|
+
const undoStateChange = new ReplaceState(stateChange.replacement, stateChange.entity);
|
|
217
|
+
this.undoStateChanges.push(undoStateChange);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
211
220
|
}
|
|
212
221
|
}
|
|
213
222
|
|
|
@@ -231,6 +240,13 @@ class State {
|
|
|
231
240
|
this.entityManager.update(index, stateChange.properties);
|
|
232
241
|
}
|
|
233
242
|
}
|
|
243
|
+
|
|
244
|
+
if (stateChange instanceof ReplaceState) {
|
|
245
|
+
const index = this.entityManager.indexOf(stateChange.entity);
|
|
246
|
+
if (index !== -1) {
|
|
247
|
+
this.entityManager.replace(index, stateChange.replacement);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
234
250
|
}
|
|
235
251
|
}
|
|
236
252
|
}
|
|
@@ -286,3 +302,16 @@ export class UpdateState extends StateChange {
|
|
|
286
302
|
super(entity, properties);
|
|
287
303
|
}
|
|
288
304
|
}
|
|
305
|
+
|
|
306
|
+
/** Replace State Class */
|
|
307
|
+
export class ReplaceState {
|
|
308
|
+
/**
|
|
309
|
+
* Create Replace State
|
|
310
|
+
* @param {object} entity - the entity to be replaced (used to locate it by reference)
|
|
311
|
+
* @param {object} replacement - the entity to put in its place
|
|
312
|
+
*/
|
|
313
|
+
constructor(entity, replacement) {
|
|
314
|
+
this.entity = entity;
|
|
315
|
+
this.replacement = replacement;
|
|
316
|
+
}
|
|
317
|
+
}
|
package/core/lib/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { Logging } from './logging.js';
|
|
3
3
|
import { Constants } from './constants.js';
|
|
4
|
+
import { Property } from '../properties/property.js';
|
|
4
5
|
|
|
5
6
|
/** Utils Class */
|
|
6
7
|
export class Utils {
|
|
@@ -46,26 +47,26 @@ export class Utils {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
|
-
* Sort points around an arc
|
|
50
|
+
* Sort points around an arc centre by angular position.
|
|
50
51
|
* Mutates the points array.
|
|
51
52
|
*
|
|
52
53
|
* @param {Array} points - array of points
|
|
53
54
|
* @param {Point} startPoint - arc start point
|
|
54
55
|
* @param {Point} endPoint - arc end point
|
|
55
|
-
* @param {Point}
|
|
56
|
+
* @param {Point} centrePoint - arc centre point
|
|
56
57
|
* @param {number} direction - arc direction (ccw >0, cw <=0)
|
|
57
58
|
*
|
|
58
59
|
*/
|
|
59
|
-
static sortPointsOnArc(points, startPoint, endPoint,
|
|
60
|
+
static sortPointsOnArc(points, startPoint, endPoint, centrePoint, direction = 1) {
|
|
60
61
|
if (!Array.isArray(points)) return;
|
|
61
62
|
|
|
62
|
-
const refAngle = direction > 0 ?
|
|
63
|
+
const refAngle = direction > 0 ? centrePoint.angle(startPoint) : centrePoint.angle(endPoint);
|
|
63
64
|
const twoPi = Math.PI * 2;
|
|
64
65
|
const normalize = (ang) => ((ang % twoPi) + twoPi) % twoPi;
|
|
65
66
|
|
|
66
67
|
// map points to angles
|
|
67
68
|
const mapped = points.map((point) => {
|
|
68
|
-
const angle = Math.atan2(point.y -
|
|
69
|
+
const angle = Math.atan2(point.y - centrePoint.y, point.x - centrePoint.x);
|
|
69
70
|
const normalizedAngle = normalize(angle - refAngle);
|
|
70
71
|
return { point, normalizedAngle };
|
|
71
72
|
});
|
|
@@ -114,13 +115,31 @@ export class Utils {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// Clear handle so a fresh unique handle is assigned when added to the scene
|
|
117
|
-
if (clone.
|
|
118
|
-
clone.
|
|
118
|
+
if (clone.getProperty?.(Property.Names.HANDLE) !== undefined) {
|
|
119
|
+
clone.setProperty?.(Property.Names.HANDLE, undefined);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
return clone;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Copy display properties from a source entity into a plain data object.
|
|
127
|
+
* Useful when creating a new entity that should inherit the visual properties
|
|
128
|
+
* of an existing one (e.g. the arc produced by Fillet).
|
|
129
|
+
* @param {Entity} entity - source entity
|
|
130
|
+
* @param {Array<string>} properties - property names to copy
|
|
131
|
+
* @return {Object} plain object containing the copied properties
|
|
132
|
+
*/
|
|
133
|
+
static cloneProperties(entity, properties = ['colour', 'layer', 'lineType', 'lineWidth', 'height', 'styleName', 'dimensionStyle', 'patternName', 'scale']) {
|
|
134
|
+
const result = {};
|
|
135
|
+
for (const prop of properties) {
|
|
136
|
+
if (entity.properties?.has(prop)) {
|
|
137
|
+
result[prop] = entity.getProperty(prop);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
124
143
|
/**
|
|
125
144
|
* Calculate area of triangle given 3 points
|
|
126
145
|
* @param {Point} p1
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Property } from './property.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EntityProperties — typed property store for an entity.
|
|
5
|
+
*
|
|
6
|
+
* Holds a plain-object store of named Property descriptors.
|
|
7
|
+
* Each descriptor owns its value.
|
|
8
|
+
* Using a plain object (rather than a private Map) ensures the store is deep-cloneable by Utils.cloneObject.
|
|
9
|
+
*
|
|
10
|
+
* Computed properties may supply get(entity) and set(entity, value) functions.
|
|
11
|
+
* These are stateless functions (not bound methods) so they survive cloneObject.
|
|
12
|
+
*/
|
|
13
|
+
export class EntityProperties {
|
|
14
|
+
/** @type {Object.<string, Property>} */
|
|
15
|
+
// Store is a plain object to ensure it is deep-cloneable by Utils.cloneObject.
|
|
16
|
+
_store = {};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register a property.
|
|
20
|
+
* @param {string} name - property name
|
|
21
|
+
* @param {Object} definition - passed directly to the Property constructor
|
|
22
|
+
* @param {string} definition.type - Property.Type constant
|
|
23
|
+
* @param {any} definition.value - initial value
|
|
24
|
+
* @param {boolean} [definition.readOnly=false]
|
|
25
|
+
* @param {boolean} [definition.visible=true]
|
|
26
|
+
* @param {number} [definition.dxfCode]
|
|
27
|
+
* @param {Function} [definition.get] - computed getter: (entity) => value
|
|
28
|
+
* @param {Function} [definition.set] - computed setter: (entity, value) => void
|
|
29
|
+
*/
|
|
30
|
+
add(name, definition) {
|
|
31
|
+
this._store[name] = new Property(definition);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the current value of a property.
|
|
36
|
+
* For computed properties, invokes get(entity).
|
|
37
|
+
* For stored properties, returns prop.value directly.
|
|
38
|
+
* @param {string} name
|
|
39
|
+
* @param {Object} [entity] - passed to the computed getter when prop.get is defined
|
|
40
|
+
* @return {any} value, or undefined if not registered
|
|
41
|
+
*/
|
|
42
|
+
get(name, entity) {
|
|
43
|
+
const prop = this._store[name];
|
|
44
|
+
if (!prop) return undefined;
|
|
45
|
+
if (prop.get) return prop.get(entity);
|
|
46
|
+
return prop.value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set the value of a property.
|
|
51
|
+
* For computed properties, invokes set(entity, value).
|
|
52
|
+
* For stored properties, updates prop.value directly.
|
|
53
|
+
* No-op if the property is not registered or is readOnly.
|
|
54
|
+
* @param {string} name
|
|
55
|
+
* @param {any} value
|
|
56
|
+
* @param {Object} [entity] - passed to the computed setter when prop.set is defined
|
|
57
|
+
*/
|
|
58
|
+
set(name, value, entity) {
|
|
59
|
+
const prop = this._store[name];
|
|
60
|
+
if (!prop) return;
|
|
61
|
+
if (prop.readOnly) return;
|
|
62
|
+
if (prop.set) {
|
|
63
|
+
prop.set(entity, value);
|
|
64
|
+
} else {
|
|
65
|
+
prop.value = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check whether a property is registered.
|
|
71
|
+
* @param {string} name
|
|
72
|
+
* @return {boolean}
|
|
73
|
+
*/
|
|
74
|
+
has(name) {
|
|
75
|
+
return name in this._store;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Remove a property from the store.
|
|
80
|
+
* @param {string} name
|
|
81
|
+
*/
|
|
82
|
+
remove(name) {
|
|
83
|
+
delete this._store[name];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Return names of all visible properties in registration order.
|
|
88
|
+
* @return {string[]}
|
|
89
|
+
*/
|
|
90
|
+
list() {
|
|
91
|
+
return Object.entries(this._store)
|
|
92
|
+
.filter(([, prop]) => prop.visible)
|
|
93
|
+
.map(([name]) => name);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Return the full Property descriptor for a named property.
|
|
98
|
+
* @param {string} name
|
|
99
|
+
* @return {Property|undefined}
|
|
100
|
+
*/
|
|
101
|
+
definition(name) {
|
|
102
|
+
return this._store[name];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -2,8 +2,112 @@ import { Flags } from './flags.js';
|
|
|
2
2
|
|
|
3
3
|
/** Property Class */
|
|
4
4
|
export class Property {
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Create a Property descriptor
|
|
7
|
+
* @param {Object} [options]
|
|
8
|
+
* @param {string} options.type - Property.Type constant describing the value kind
|
|
9
|
+
* @param {any} options.value - initial value
|
|
10
|
+
* @param {boolean} [options.readOnly=false] - when true, set() is a no-op
|
|
11
|
+
* @param {boolean} [options.visible=true] - controls whether the property appears in listings
|
|
12
|
+
* @param {number} [options.dxfCode] - associated DXF group code (informational)
|
|
13
|
+
* @param {Function} [options.get] - computed getter: (entity) => value
|
|
14
|
+
* @param {Function} [options.set] - computed setter: (entity, value) => void
|
|
15
|
+
*/
|
|
16
|
+
constructor({ type, value, readOnly = false, visible = true, dxfCode, get, set, options } = {}) {
|
|
17
|
+
this.type = type;
|
|
18
|
+
this._value = value;
|
|
19
|
+
this.readOnly = readOnly;
|
|
20
|
+
this.visible = visible;
|
|
21
|
+
this.dxfCode = dxfCode;
|
|
22
|
+
this.get = get;
|
|
23
|
+
this.set = set;
|
|
24
|
+
this.options = options;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @return {any} current value */
|
|
28
|
+
get value() {
|
|
29
|
+
return this._value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @param {any} val - new value; ignored when readOnly */
|
|
33
|
+
set value(val) {
|
|
34
|
+
if (!this.readOnly) {
|
|
35
|
+
this._value = val;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Property type constants
|
|
41
|
+
* Used to describe the type of a property value for UI rendering and validation.
|
|
42
|
+
* - NUMBER: numeric value (integer or float)
|
|
43
|
+
* - STRING: text value
|
|
44
|
+
* - BOOLEAN: true/false toggle
|
|
45
|
+
* - LIST: selection from a named set (layer, lineType, hatch pattern, etc.)
|
|
46
|
+
* - COLOUR: RGB colour object
|
|
47
|
+
* - LABEL: read-only display value (no editing)
|
|
48
|
+
*/
|
|
49
|
+
static Type = {
|
|
50
|
+
NUMBER: 'NUMBER',
|
|
51
|
+
STRING: 'STRING',
|
|
52
|
+
BOOLEAN: 'BOOLEAN',
|
|
53
|
+
LIST: 'LIST',
|
|
54
|
+
COLOUR: 'COLOUR',
|
|
55
|
+
LABEL: 'LABEL',
|
|
56
|
+
ENTITIES: 'ENTITIES',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Canonical property name constants.
|
|
61
|
+
* Use these instead of raw strings when calling getProperty() / setProperty().
|
|
62
|
+
*/
|
|
63
|
+
static Names = Object.freeze({
|
|
64
|
+
// ── Base entity (Entity class) ────────────────────────────────────────────
|
|
65
|
+
HANDLE: 'handle',
|
|
66
|
+
LAYER: 'layer',
|
|
67
|
+
LINETYPE: 'lineType',
|
|
68
|
+
LINEWIDTH: 'lineWidth',
|
|
69
|
+
COLOUR: 'colour',
|
|
70
|
+
POINTS: 'points',
|
|
71
|
+
// ── Shared (multiple entities) ────────────────────────────────────────────
|
|
72
|
+
ANGLE: 'angle',
|
|
73
|
+
BLOCKNAME: 'blockName',
|
|
74
|
+
DIRECTION: 'direction',
|
|
75
|
+
HEIGHT: 'height',
|
|
76
|
+
RADIUS: 'radius',
|
|
77
|
+
ROTATION: 'rotation',
|
|
78
|
+
SCALE: 'scale',
|
|
79
|
+
STRING: 'string',
|
|
80
|
+
STYLENAME: 'styleName',
|
|
81
|
+
// ── PolylineBase ──────────────────────────────────────────────────────────
|
|
82
|
+
CLOSED: 'closed',
|
|
83
|
+
// ── Hatch ─────────────────────────────────────────────────────────────────
|
|
84
|
+
CHILDENTITIES: 'childEntities',
|
|
85
|
+
PATTERNNAME: 'patternName',
|
|
86
|
+
// ── Text ──────────────────────────────────────────────────────────────────
|
|
87
|
+
BACKWARDS: 'backwards',
|
|
88
|
+
HORIZONTALALIGNMENT: 'horizontalAlignment',
|
|
89
|
+
UPSIDEDOWN: 'upsideDown',
|
|
90
|
+
VERTICALALIGNMENT: 'verticalAlignment',
|
|
91
|
+
// ── ArcAlignedText ────────────────────────────────────────────────────────
|
|
92
|
+
ARCSIDE: 'arcSide',
|
|
93
|
+
BOLD: 'bold',
|
|
94
|
+
CHARACTERSPACING: 'characterSpacing',
|
|
95
|
+
FONTNAME: 'fontName',
|
|
96
|
+
ITALIC: 'italic',
|
|
97
|
+
OFFSETFROMARC: 'offsetFromArc',
|
|
98
|
+
OFFSETFROMLEFT: 'offsetFromLeft',
|
|
99
|
+
OFFSETFROMRIGHT: 'offsetFromRight',
|
|
100
|
+
TEXTALIGNMENT: 'textAlignment',
|
|
101
|
+
TEXTORIENTATION: 'textOrientation',
|
|
102
|
+
TEXTREVERSED: 'textReversed',
|
|
103
|
+
UNDERLINE: 'underline',
|
|
104
|
+
WIDTHFACTOR: 'widthFactor',
|
|
105
|
+
// ── Dimensions ────────────────────────────────────────────────────────────
|
|
106
|
+
DIMENSIONSTYLE: 'dimensionStyle',
|
|
107
|
+
LEADERLENGTH: 'leaderLength',
|
|
108
|
+
LINEARDIMANGLE: 'linearDimAngle',
|
|
109
|
+
TEXTOVERRIDE: 'textOverride',
|
|
110
|
+
});
|
|
7
111
|
|
|
8
112
|
/**
|
|
9
113
|
* Parse the input values and return a value
|