@design-core/core 0.0.7 → 51.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/.github/workflows/DesignCoreQA.yml +3 -0
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +7 -7
- package/core/dimensions/angularDimension.js +32 -32
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +19 -19
- package/core/dimensions/diametricDimension.js +10 -10
- package/core/dimensions/dimension.js +42 -42
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +17 -17
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +9 -9
- package/core/dimensions/radialDimension.js +8 -8
- package/core/dimensions/rotatedDimension.js +8 -8
- package/core/entities/arc.js +22 -22
- package/core/entities/arctext.js +13 -13
- package/core/entities/circle.js +7 -7
- package/core/entities/hatch.js +210 -40
- package/core/entities/insert.js +11 -6
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +3 -3
- package/core/entities/{basePolyline.js → polylineBase.js} +18 -18
- package/core/entities/text.js +4 -4
- package/core/lib/boundingBox.js +11 -11
- package/core/lib/canvas.js +13 -6
- package/core/lib/clipboard.js +2 -2
- package/core/lib/dxf/dxf.js +11 -11
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +19 -19
- package/core/lib/inputManager.js +1 -1
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +1 -1
- package/core/lib/polylineUtils.js +9 -9
- package/core/lib/renderers/cairoRenderer.js +5 -5
- package/core/lib/renderers/canvasRenderer.js +4 -4
- package/core/lib/renderers/pdfRenderer.js +7 -7
- package/core/lib/renderers/rendererBase.js +1 -1
- package/core/lib/scene.js +1 -1
- package/core/lib/selectionManager.js +18 -18
- package/core/lib/utils.js +5 -5
- package/core/properties/entityProperties.js +2 -1
- package/core/properties/flags.js +12 -0
- package/core/properties/property.js +8 -3
- package/core/properties/propertyManager.js +52 -37
- package/core/tables/block.js +50 -46
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +6 -6
- package/core/tools/chamferFilletBase.js +4 -4
- package/core/tools/copy.js +8 -8
- 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 +6 -6
- package/core/tools/extend.js +13 -13
- package/core/tools/fillet.js +6 -6
- package/core/tools/matchProp.js +1 -1
- package/core/tools/mirror.js +11 -11
- package/core/tools/move.js +5 -5
- package/core/tools/offset.js +29 -29
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +16 -16
- package/core/tools/scale.js +6 -6
- package/core/tools/trim.js +15 -15
- package/cspell.json +323 -0
- package/package.json +1 -1
- package/test/dimensions/alignedDimension.test.js +3 -3
- package/test/dimensions/angularDimension.test.js +7 -7
- package/test/dimensions/diametricDimension.test.js +9 -9
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +12 -12
- package/test/dimensions/{baseLinearDimension.test.js → linearDimensionBase.test.js} +7 -7
- package/test/dimensions/radialDimension.test.js +9 -9
- package/test/dimensions/rotatedDimension.test.js +4 -4
- package/test/entities/arc.test.js +10 -10
- package/test/entities/arctext.test.js +6 -6
- package/test/entities/circle.test.js +18 -18
- package/test/entities/hatch.test.js +362 -39
- package/test/entities/insert.test.js +9 -9
- package/test/entities/point.test.js +3 -3
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +131 -62
- package/test/entities/text.test.js +19 -19
- package/test/integration/exportIntegration.test.js +3 -3
- package/test/integration/userInputIntegration.test.js +5 -5
- package/test/lib/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +9 -9
- package/test/lib/clipboard.test.js +73 -0
- package/test/lib/inputManager.test.js +4 -4
- package/test/lib/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/snapping.test.js +1 -1
- package/test/properties/flags.test.js +24 -0
- package/test/properties/propertyManager.test.js +93 -22
- package/test/renderers/pdfRenderer.test.js +2 -2
- package/test/tables/block.test.js +63 -14
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +3 -3
- package/test/test-helpers/test-helpers.js +6 -6
- package/test/tools/chamfer.test.js +60 -60
- package/test/tools/chamferFilletBase.test.js +36 -36
- package/test/tools/copy.test.js +6 -6
- 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 +2 -2
- package/test/tools/extend.test.js +84 -84
- package/test/tools/fillet.test.js +62 -62
- package/test/tools/matchProp.test.js +6 -6
- package/test/tools/mirror.test.js +26 -26
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +65 -65
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +10 -10
- package/test/tools/scale.test.js +28 -28
- package/test/tools/trim.test.js +109 -109
- package/test/tools/zoom.test.js +15 -17
package/core/entities/hatch.js
CHANGED
|
@@ -19,6 +19,7 @@ import { Line } from './line.js';
|
|
|
19
19
|
import { Arc } from './arc.js';
|
|
20
20
|
import { Polyline } from './polyline.js';
|
|
21
21
|
import { Property } from '../properties/property.js';
|
|
22
|
+
import { PatternLine } from '../lib/patterns.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Hatch Entity Class
|
|
@@ -41,6 +42,14 @@ export class Hatch extends Entity {
|
|
|
41
42
|
enumerable: false,
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
// resolved PatternLine array — populated at construction from inline DXF
|
|
46
|
+
// data (type 0/2) or from the Patterns library (type 1)
|
|
47
|
+
Object.defineProperty(this, 'patternLines', {
|
|
48
|
+
value: [],
|
|
49
|
+
writable: true,
|
|
50
|
+
enumerable: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
44
53
|
// Boundary shapes stored in EntityProperties so all entity data lives in one place.
|
|
45
54
|
this.properties.add(Property.Names.CHILDENTITIES, {
|
|
46
55
|
type: Property.Type.ENTITIES,
|
|
@@ -53,14 +62,24 @@ export class Hatch extends Entity {
|
|
|
53
62
|
|
|
54
63
|
const rawPatternName = String(Property.loadValue([data?.[2], data?.patternName], 'ANSI31')).toUpperCase();
|
|
55
64
|
// DXF Groupcode 70 - Solid Fill Flag (1 = solid, 0 = pattern): if set, override pattern name
|
|
56
|
-
|
|
65
|
+
// DXF Groupcode 76 - Hatch pattern type: 0 = User-defined ('U'); 1 = Predefined; 2 = Custom
|
|
66
|
+
const patternType = data?.[76] ?? 1;
|
|
67
|
+
// User-defined hatches (76=0) are normalised to the sentinel name 'U'
|
|
68
|
+
// so the pattern type is implied by the name rather than stored separately.
|
|
69
|
+
const resolvedPatternName = data?.[70] === 1 ? 'SOLID' : patternType === 0 ? 'U' : rawPatternName;
|
|
70
|
+
if (patternType === 1 && resolvedPatternName !== 'SOLID' && !Patterns.patternExists(resolvedPatternName)) {
|
|
71
|
+
Logging.instance.warn(`Hatch: pattern '${resolvedPatternName}' not found`);
|
|
72
|
+
}
|
|
57
73
|
|
|
58
74
|
// DXF Groupcode 2 - Hatch pattern name
|
|
59
75
|
this.properties.add(Property.Names.PATTERNNAME, {
|
|
60
76
|
type: Property.Type.LIST,
|
|
61
77
|
value: resolvedPatternName,
|
|
62
78
|
dxfCode: 2,
|
|
63
|
-
options: () =>
|
|
79
|
+
options: () => [
|
|
80
|
+
{ display: 'User Defined', value: 'U' },
|
|
81
|
+
...Object.keys(Patterns.hatch_patterns).map((name) => ({ display: name, value: name })),
|
|
82
|
+
],
|
|
64
83
|
});
|
|
65
84
|
// DXF Groupcode 41 - Hatch pattern scale
|
|
66
85
|
this.properties.add(Property.Names.SCALE, {
|
|
@@ -74,6 +93,28 @@ export class Hatch extends Entity {
|
|
|
74
93
|
value: Property.loadValue([data?.[52], data?.angle], 0),
|
|
75
94
|
dxfCode: 52,
|
|
76
95
|
});
|
|
96
|
+
// DXF Groupcode 47 - Line spacing (user-defined hatches only)
|
|
97
|
+
this.properties.add(Property.Names.HATCHSPACING, {
|
|
98
|
+
type: Property.Type.NUMBER,
|
|
99
|
+
value: Property.loadValue([data?.[47]], 1),
|
|
100
|
+
dxfCode: 47,
|
|
101
|
+
readOnly: (entity) => entity.getProperty(Property.Names.PATTERNNAME) !== 'U',
|
|
102
|
+
});
|
|
103
|
+
// DXF Groupcode 77 - Double flag (user-defined hatches only): 0 = not double; 1 = double
|
|
104
|
+
this.properties.add(Property.Names.HATCHDOUBLE, {
|
|
105
|
+
type: Property.Type.BOOLEAN,
|
|
106
|
+
value: (data?.[77] ?? 0) === 1,
|
|
107
|
+
dxfCode: 77,
|
|
108
|
+
readOnly: (entity) => entity.getProperty(Property.Names.PATTERNNAME) !== 'U',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Populate patternLines: inline data for user-defined (76=0) and custom
|
|
112
|
+
// (76=2) patterns; Patterns library for predefined (76=1, default).
|
|
113
|
+
if ((patternType === 0 || patternType === 2) && data) {
|
|
114
|
+
this.patternLines = this.#buildInlinePatternLines(data);
|
|
115
|
+
} else if (resolvedPatternName !== 'SOLID' && Patterns.patternExists(resolvedPatternName)) {
|
|
116
|
+
this.patternLines = this._copyLibraryPattern(resolvedPatternName);
|
|
117
|
+
}
|
|
77
118
|
|
|
78
119
|
// add a single point to this.points if no other points exist
|
|
79
120
|
if (!this.points.length) {
|
|
@@ -103,6 +144,116 @@ export class Hatch extends Entity {
|
|
|
103
144
|
}
|
|
104
145
|
}
|
|
105
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Return unfrozen copies of library PatternLine objects for a named pattern.
|
|
149
|
+
* Patterns.getPattern() returns frozen objects (shared cache); cloneObject()
|
|
150
|
+
* uses Reflect.ownKeys and would fail writing into frozen instances.
|
|
151
|
+
* @param {string} name - upper-case pattern name
|
|
152
|
+
* @return {Array<PatternLine>} array of mutable PatternLine copies
|
|
153
|
+
*/
|
|
154
|
+
_copyLibraryPattern(name) {
|
|
155
|
+
return Patterns.getPattern(name).map((pl) => {
|
|
156
|
+
const copy = new PatternLine();
|
|
157
|
+
copy.angle = pl.angle;
|
|
158
|
+
copy.xOrigin = pl.xOrigin;
|
|
159
|
+
copy.yOrigin = pl.yOrigin;
|
|
160
|
+
copy.xDelta = pl.xDelta;
|
|
161
|
+
copy.yDelta = pl.yDelta;
|
|
162
|
+
copy.dashes = [...pl.dashes];
|
|
163
|
+
return copy;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Synthesise PatternLine objects for a user-defined hatch from line spacing.
|
|
169
|
+
* Used when the pattern name is 'U' and the pattern has no explicit family data.
|
|
170
|
+
* @param {number} spacing - perpendicular distance between parallel lines
|
|
171
|
+
* @param {boolean} isDouble - when true, add a second family at 90° to the first
|
|
172
|
+
* @return {Array<PatternLine>}
|
|
173
|
+
*/
|
|
174
|
+
_buildSpacingPatternLines(spacing, isDouble) {
|
|
175
|
+
const pl = new PatternLine();
|
|
176
|
+
pl.angle = 0;
|
|
177
|
+
pl.xOrigin = 0;
|
|
178
|
+
pl.yOrigin = 0;
|
|
179
|
+
pl.xDelta = 0;
|
|
180
|
+
pl.yDelta = spacing;
|
|
181
|
+
pl.dashes = [];
|
|
182
|
+
const result = [pl];
|
|
183
|
+
if (isDouble) {
|
|
184
|
+
const pl2 = new PatternLine();
|
|
185
|
+
pl2.angle = 90;
|
|
186
|
+
pl2.xOrigin = 0;
|
|
187
|
+
pl2.yOrigin = 0;
|
|
188
|
+
pl2.xDelta = 0;
|
|
189
|
+
pl2.yDelta = spacing;
|
|
190
|
+
pl2.dashes = [];
|
|
191
|
+
result.push(pl2);
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build PatternLine objects from inline DXF pattern data (group code 76 = 0 or 2).
|
|
198
|
+
* DXF stores deltas pre-multiplied by scale; we divide by scale to recover the
|
|
199
|
+
* normalised (scale=1) values that buildPatternCache expects.
|
|
200
|
+
* @param {Object} data - raw DXF group-code data for the HATCH entity
|
|
201
|
+
* @return {Array<PatternLine>} array of PatternLine objects
|
|
202
|
+
*/
|
|
203
|
+
#buildInlinePatternLines(data) {
|
|
204
|
+
const scale = data[41] ?? 1;
|
|
205
|
+
// DXF group code 53 per-family angles are written by AutoCAD as absolute
|
|
206
|
+
// angles that already include the entity rotation (group code 52). Subtract
|
|
207
|
+
// the entity angle here so that buildPatternCache's addition of ANGLE
|
|
208
|
+
// restores the correct absolute rotation without doubling it.
|
|
209
|
+
const entityAngle = data[52] ?? 0;
|
|
210
|
+
const angles = [].concat(data[53] ?? []);
|
|
211
|
+
const dxDeltas = [].concat(data[45] ?? []);
|
|
212
|
+
const dyDeltas = [].concat(data[46] ?? []);
|
|
213
|
+
const dashCounts = [].concat(data[79] ?? []);
|
|
214
|
+
const allDashes = [].concat(data[49] ?? []);
|
|
215
|
+
|
|
216
|
+
// Pure user-defined hatch (76=0): no inline family data — synthesise
|
|
217
|
+
// from group code 47 (line spacing) and 77 (double flag).
|
|
218
|
+
if (!angles.length) {
|
|
219
|
+
return this._buildSpacingPatternLines((data[47] ?? 1) / scale, data[77] === 1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const patternLines = [];
|
|
223
|
+
let dashIdx = 0;
|
|
224
|
+
|
|
225
|
+
angles.forEach((angle, i) => {
|
|
226
|
+
const pl = new PatternLine();
|
|
227
|
+
pl.angle = angle - entityAngle;
|
|
228
|
+
pl.xOrigin = 0;
|
|
229
|
+
pl.yOrigin = 0;
|
|
230
|
+
// DXF deltas are already in drawing units, pre-multiplied by scale; divide out.
|
|
231
|
+
pl.xDelta = (dxDeltas[i] ?? 0) / scale;
|
|
232
|
+
pl.yDelta = (dyDeltas[i] ?? 0) / scale;
|
|
233
|
+
|
|
234
|
+
const count = dashCounts[i] ?? 0;
|
|
235
|
+
pl.dashes = allDashes.slice(dashIdx, dashIdx + count).map((d) => d / scale);
|
|
236
|
+
dashIdx += count;
|
|
237
|
+
|
|
238
|
+
patternLines.push(pl);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// DXF group code 77: double flag — add a perpendicular family
|
|
242
|
+
if (data[77] === 1 && patternLines.length > 0) {
|
|
243
|
+
const first = patternLines[0];
|
|
244
|
+
const pl = new PatternLine();
|
|
245
|
+
pl.angle = first.angle + 90;
|
|
246
|
+
pl.xOrigin = 0;
|
|
247
|
+
pl.yOrigin = 0;
|
|
248
|
+
pl.xDelta = first.xDelta;
|
|
249
|
+
pl.yDelta = first.yDelta;
|
|
250
|
+
pl.dashes = [...first.dashes];
|
|
251
|
+
patternLines.push(pl);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return patternLines;
|
|
255
|
+
}
|
|
256
|
+
|
|
106
257
|
/**
|
|
107
258
|
* Build and cache world-space line segments for the current pattern.
|
|
108
259
|
* For pattern hatches, segments are pre-clipped against the boundary using
|
|
@@ -114,7 +265,7 @@ export class Hatch extends Entity {
|
|
|
114
265
|
return;
|
|
115
266
|
}
|
|
116
267
|
|
|
117
|
-
if (
|
|
268
|
+
if (!this.patternLines.length) {
|
|
118
269
|
this.cachedPattern = [];
|
|
119
270
|
return;
|
|
120
271
|
}
|
|
@@ -141,13 +292,13 @@ export class Hatch extends Entity {
|
|
|
141
292
|
}
|
|
142
293
|
}
|
|
143
294
|
|
|
144
|
-
const
|
|
295
|
+
const centrePoint = bb.centrePoint;
|
|
145
296
|
const bbXLength = bb.xLength;
|
|
146
297
|
const bbYLength = bb.yLength;
|
|
147
298
|
const s = this.getProperty(Property.Names.SCALE);
|
|
148
299
|
const lines = [];
|
|
149
300
|
|
|
150
|
-
const pattern =
|
|
301
|
+
const pattern = this.patternLines;
|
|
151
302
|
pattern.forEach((patternLine) => {
|
|
152
303
|
let dashLength = bbXLength / 2;
|
|
153
304
|
if (patternLine.dashes.length) {
|
|
@@ -175,8 +326,8 @@ export class Hatch extends Entity {
|
|
|
175
326
|
|
|
176
327
|
// World-space anchor for this family: bounding-box centre shifted by the
|
|
177
328
|
// pattern line's origin offset (scaled to match the hatch scale).
|
|
178
|
-
const cx =
|
|
179
|
-
const cy =
|
|
329
|
+
const cx = centrePoint.x + patternLine.xOrigin * s;
|
|
330
|
+
const cy = centrePoint.y + patternLine.yOrigin * s;
|
|
180
331
|
|
|
181
332
|
// Bounding-box half-extents in pattern-local (unscaled) space.
|
|
182
333
|
const halfW = bbXLength / (2 * s);
|
|
@@ -390,13 +541,13 @@ export class Hatch extends Entity {
|
|
|
390
541
|
// 10 - X; 20 - Y; 40 - Radius; 50 - Start Angle; 51 - End Angle; 73 - Is Counter Clockwise
|
|
391
542
|
const shapeData = { points: [] };
|
|
392
543
|
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
544
|
+
const centrePoint = points.shift();
|
|
545
|
+
if (centrePoint.sequence !== undefined && centrePoint.sequence !== 10) {
|
|
395
546
|
const msg = 'Invalid point sequence';
|
|
396
547
|
const err = (`${this.type} - ${msg}`);
|
|
397
548
|
throw Error(err);
|
|
398
549
|
}
|
|
399
|
-
shapeData.points.push(
|
|
550
|
+
shapeData.points.push(centrePoint);
|
|
400
551
|
shapeData.radius = this.getDataValue(data, 40);
|
|
401
552
|
shapeData.startAngle = this.getDataValue(data, 50);
|
|
402
553
|
shapeData.endAngle = this.getDataValue(data, 51);
|
|
@@ -482,8 +633,8 @@ export class Hatch extends Entity {
|
|
|
482
633
|
const result = await DesignCore.Scene.inputManager.requestInput(op);
|
|
483
634
|
if (result === undefined) return;
|
|
484
635
|
|
|
485
|
-
const
|
|
486
|
-
const boundary = this.processSelection(
|
|
636
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities.slice(0);
|
|
637
|
+
const boundary = this.processSelection(selectedEntities);
|
|
487
638
|
|
|
488
639
|
if (boundary.length) {
|
|
489
640
|
this.setProperty(Property.Names.CHILDENTITIES, boundary);
|
|
@@ -506,8 +657,8 @@ export class Hatch extends Entity {
|
|
|
506
657
|
* Preview the entity during creation
|
|
507
658
|
*/
|
|
508
659
|
preview() {
|
|
509
|
-
const
|
|
510
|
-
const shapes = this.processSelection(
|
|
660
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities.slice(0);
|
|
661
|
+
const shapes = this.processSelection(selectedEntities);
|
|
511
662
|
if (shapes.length) {
|
|
512
663
|
DesignCore.Scene.previewEntities.create(this.type, { points: this.points, childEntities: shapes }); // childEntities is passed as plain data here, not via getProperty
|
|
513
664
|
}
|
|
@@ -515,31 +666,31 @@ export class Hatch extends Entity {
|
|
|
515
666
|
|
|
516
667
|
/**
|
|
517
668
|
* Convert the selection to boundary items
|
|
518
|
-
* @param {Array}
|
|
669
|
+
* @param {Array} selectedEntities
|
|
519
670
|
* @return {Array} - Array of boundary items
|
|
520
671
|
*/
|
|
521
|
-
processSelection(
|
|
672
|
+
processSelection(selectedEntities) {
|
|
522
673
|
const selectedchildEntities = [];
|
|
523
674
|
|
|
524
675
|
let iterationPoints = [];
|
|
525
676
|
|
|
526
677
|
// track the last index count to make sure the loop is closed
|
|
527
|
-
let lastIndexCount =
|
|
678
|
+
let lastIndexCount = selectedEntities.length + 1;
|
|
528
679
|
|
|
529
|
-
while (
|
|
530
|
-
if (
|
|
680
|
+
while (selectedEntities.length) {
|
|
681
|
+
if (selectedEntities.length === lastIndexCount) {
|
|
531
682
|
return [];
|
|
532
683
|
}
|
|
533
684
|
|
|
534
|
-
lastIndexCount =
|
|
685
|
+
lastIndexCount = selectedEntities.length;
|
|
535
686
|
|
|
536
|
-
for (let i = 0; i <
|
|
537
|
-
// no points collected - get the first index from selected
|
|
687
|
+
for (let i = 0; i < selectedEntities.length; i++) {
|
|
688
|
+
// no points collected - get the first index from selected entities
|
|
538
689
|
|
|
539
|
-
const currentItem =
|
|
540
|
-
// if the item can't be converted to polyline points, remove from selected
|
|
690
|
+
const currentItem = selectedEntities[i];
|
|
691
|
+
// if the item can't be converted to polyline points, remove from selected entities and go again
|
|
541
692
|
if (typeof currentItem.toPolylinePoints === 'undefined' || currentItem.type === 'Text' || currentItem.type === 'ArcAlignedText') {
|
|
542
|
-
|
|
693
|
+
selectedEntities.splice(i, 1);
|
|
543
694
|
break;
|
|
544
695
|
}
|
|
545
696
|
|
|
@@ -575,8 +726,8 @@ export class Hatch extends Entity {
|
|
|
575
726
|
iterationPoints.push(...currentPoints);
|
|
576
727
|
}
|
|
577
728
|
|
|
578
|
-
// remove the index from selected
|
|
579
|
-
|
|
729
|
+
// remove the index from selected entities
|
|
730
|
+
selectedEntities = selectedEntities.filter((index) => index !== selectedEntities[i]);
|
|
580
731
|
|
|
581
732
|
if (iterationPoints.at(0).isSame(iterationPoints.at(-1))) {
|
|
582
733
|
const shape = new Polyline();
|
|
@@ -613,7 +764,7 @@ export class Hatch extends Entity {
|
|
|
613
764
|
// Build cache if stale (cachedPattern === null means never built or invalidated)
|
|
614
765
|
if (this.cachedPattern === null) this.buildPatternCache();
|
|
615
766
|
|
|
616
|
-
if (this.getProperty(Property.Names.PATTERNNAME) === 'SOLID') {
|
|
767
|
+
if (this.cachedPattern.length === 0 && this.getProperty(Property.Names.PATTERNNAME) === 'SOLID') {
|
|
617
768
|
// Design renders solid hatches with a direct fill() call rather than the
|
|
618
769
|
// dense cross-hatch line pattern used by commercial CAD applications
|
|
619
770
|
// (Commercial CAD's SOLID pattern uses two line families at 0.0001-unit spacing).
|
|
@@ -635,7 +786,7 @@ export class Hatch extends Entity {
|
|
|
635
786
|
}
|
|
636
787
|
|
|
637
788
|
// Draw the boundary outline when hovered or selected.
|
|
638
|
-
const isSelected = DesignCore.Scene.selectionManager.
|
|
789
|
+
const isSelected = DesignCore.Scene.selectionManager.selectedEntities.includes(this);
|
|
639
790
|
const isHovered = DesignCore.Scene.hoverEntities.indexOf(this) !== -1;
|
|
640
791
|
if (isSelected || isHovered) {
|
|
641
792
|
renderer.setDash([], 0);
|
|
@@ -694,17 +845,20 @@ export class Hatch extends Entity {
|
|
|
694
845
|
file.writeGroupCode('97', '0'); // Number of source boundary objects
|
|
695
846
|
}
|
|
696
847
|
file.writeGroupCode('75', '1'); // Hatch style: 0 = Hatch “odd parity” area (Normal style) 1 = Hatch outermost area only (Outer style) 2 = Hatch through entire area (Ignore style)
|
|
697
|
-
|
|
848
|
+
// Hatch pattern type: 0 = User-defined; 1 = Predefined; 2 = Custom
|
|
849
|
+
const patternName = this.getProperty(Property.Names.PATTERNNAME);
|
|
850
|
+
const isUserDefined = patternName === 'U';
|
|
851
|
+
const isLibraryPattern = !isUserDefined && patternName !== 'SOLID' && Patterns.patternExists(patternName);
|
|
852
|
+
file.writeGroupCode('76', isUserDefined ? '0' : isLibraryPattern ? '1' : '2');
|
|
698
853
|
|
|
699
|
-
if (
|
|
854
|
+
if (patternName !== 'SOLID') {
|
|
700
855
|
file.writeGroupCode('52', this.getProperty(Property.Names.ANGLE)); // Hatch Pattern angle
|
|
701
856
|
file.writeGroupCode('41', this.getProperty(Property.Names.SCALE)); // Hatch Pattern scale
|
|
702
|
-
file.writeGroupCode('77', '0'); // Hatch pattern double flag(pattern fill only): 0 = not double; 1 = double
|
|
703
|
-
file.writeGroupCode('78',
|
|
857
|
+
file.writeGroupCode('77', this.getProperty(Property.Names.HATCHDOUBLE) ? '1' : '0'); // Hatch pattern double flag(pattern fill only): 0 = not double; 1 = double
|
|
858
|
+
file.writeGroupCode('78', this.patternLines.length); // Number of pattern definition lines
|
|
704
859
|
|
|
705
860
|
// Pattern data
|
|
706
|
-
|
|
707
|
-
pattern.forEach((patternLine) => {
|
|
861
|
+
this.patternLines.forEach((patternLine) => {
|
|
708
862
|
file.writeGroupCode('53', patternLine.angle + this.getProperty(Property.Names.ANGLE)); // Pattern line angle
|
|
709
863
|
file.writeGroupCode('43', patternLine.xOrigin); // Pattern line base X
|
|
710
864
|
file.writeGroupCode('44', patternLine.yOrigin); // Pattern line base y
|
|
@@ -719,7 +873,7 @@ export class Hatch extends Entity {
|
|
|
719
873
|
});
|
|
720
874
|
}
|
|
721
875
|
|
|
722
|
-
file.writeGroupCode('47', '0.5'); // pixel size
|
|
876
|
+
file.writeGroupCode('47', isUserDefined ? this.getProperty(Property.Names.HATCHSPACING) : '0.5'); // line spacing (user-defined) / pixel size
|
|
723
877
|
file.writeGroupCode('98', '1'); // Number of seed points
|
|
724
878
|
// seed points
|
|
725
879
|
file.writeGroupCode('10', '1');
|
|
@@ -774,7 +928,7 @@ export class Hatch extends Entity {
|
|
|
774
928
|
const intersect = Intersection.intersectPolylinePolyline(polyline, line);
|
|
775
929
|
const intersects = intersect.points.length;
|
|
776
930
|
// P is inside shape if there is a odd number of intersects
|
|
777
|
-
if (Math.abs(intersects % 2)
|
|
931
|
+
if (Math.abs(intersects % 2) === 1) {
|
|
778
932
|
return true;
|
|
779
933
|
}
|
|
780
934
|
}
|
|
@@ -842,6 +996,22 @@ export class Hatch extends Entity {
|
|
|
842
996
|
const upper = String(value).toUpperCase();
|
|
843
997
|
if (this.getProperty(Property.Names.PATTERNNAME) === upper) return;
|
|
844
998
|
super.setProperty(property, upper);
|
|
999
|
+
if (upper === 'U') {
|
|
1000
|
+
this.patternLines = this._buildSpacingPatternLines(
|
|
1001
|
+
this.getProperty(Property.Names.HATCHSPACING),
|
|
1002
|
+
this.getProperty(Property.Names.HATCHDOUBLE),
|
|
1003
|
+
);
|
|
1004
|
+
} else {
|
|
1005
|
+
this.patternLines = (upper !== 'SOLID' && Patterns.patternExists(upper)) ? this._copyLibraryPattern(upper) : [];
|
|
1006
|
+
}
|
|
1007
|
+
} else if (property === Property.Names.HATCHSPACING || property === Property.Names.HATCHDOUBLE) {
|
|
1008
|
+
super.setProperty(property, value);
|
|
1009
|
+
if (this.getProperty(Property.Names.PATTERNNAME) === 'U') {
|
|
1010
|
+
this.patternLines = this._buildSpacingPatternLines(
|
|
1011
|
+
this.getProperty(Property.Names.HATCHSPACING),
|
|
1012
|
+
this.getProperty(Property.Names.HATCHDOUBLE),
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
845
1015
|
} else if (property === Property.Names.POINTS) {
|
|
846
1016
|
// Special handling for hatch points to move child entities
|
|
847
1017
|
// Consider the changes from the hatch points to be an offset and rotation
|
|
@@ -850,11 +1020,11 @@ export class Hatch extends Entity {
|
|
|
850
1020
|
const theta = ang - this.points[0].angle(this.points.at(-1));
|
|
851
1021
|
|
|
852
1022
|
if (theta !== 0) {
|
|
853
|
-
// set rotation
|
|
854
|
-
const
|
|
1023
|
+
// set rotation centre
|
|
1024
|
+
const centre = this.points[0];
|
|
855
1025
|
// apply rotation to child entities points
|
|
856
1026
|
hatchChildren.forEach((child) => {
|
|
857
|
-
const rotatedPoints = child.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).rotate(
|
|
1027
|
+
const rotatedPoints = child.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).rotate(centre, theta));
|
|
858
1028
|
child.setProperty(Property.Names.POINTS, rotatedPoints);
|
|
859
1029
|
});
|
|
860
1030
|
|
package/core/entities/insert.js
CHANGED
|
@@ -51,7 +51,11 @@ export class Insert extends Entity {
|
|
|
51
51
|
// DXF Groupcode 2 - Block name
|
|
52
52
|
if (data.blockName !== undefined) {
|
|
53
53
|
const block = DesignCore.Scene.blockManager.getItemByName(data.blockName);
|
|
54
|
-
|
|
54
|
+
if (block) {
|
|
55
|
+
this.block = block;
|
|
56
|
+
} else {
|
|
57
|
+
Logging.instance.warn(`Insert: block '${data.blockName}' not found`);
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
// Named scalar rotation (internal API); DXF code 50 is handled by fromDxf
|
|
@@ -153,13 +157,13 @@ export class Insert extends Entity {
|
|
|
153
157
|
/**
|
|
154
158
|
* Draw the entity
|
|
155
159
|
* @param {Object} renderer
|
|
156
|
-
* @return {Array} block
|
|
160
|
+
* @return {Array} block entities for the canvas to render recursively
|
|
157
161
|
*/
|
|
158
162
|
draw(renderer) {
|
|
159
163
|
// blocks are associated with an insert point.
|
|
160
|
-
// Apply the insert location and rotation so block
|
|
164
|
+
// Apply the insert location and rotation so block entities draw correctly.
|
|
161
165
|
renderer.applyTransform({ x: this.points[0].x, y: this.points[0].y, rotation: Utils.degrees2radians(this.getRotation()) });
|
|
162
|
-
return this.block.
|
|
166
|
+
return this.block.entities;
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
/**
|
|
@@ -204,9 +208,9 @@ export class Insert extends Entity {
|
|
|
204
208
|
|
|
205
209
|
for (let snap = 0; snap < blockSnaps.length; snap++) {
|
|
206
210
|
const sp = blockSnaps[snap];
|
|
207
|
-
// offset the
|
|
211
|
+
// offset the entity snap point by the block insert location
|
|
208
212
|
const rotatedPoint = sp.snapPoint.add(this.points[0]);
|
|
209
|
-
// rotate the snap point to match the
|
|
213
|
+
// rotate the snap point to match the entity positions
|
|
210
214
|
const adjustedPoint = rotatedPoint.rotate(this.points[0], Utils.degrees2radians(this.getRotation()));
|
|
211
215
|
snaps.push(new SnapPoint(adjustedPoint, sp.type));
|
|
212
216
|
}
|
|
@@ -247,6 +251,7 @@ export class Insert extends Entity {
|
|
|
247
251
|
*/
|
|
248
252
|
boundingBox() {
|
|
249
253
|
const blockBB = this.block.boundingBox();
|
|
254
|
+
if (!blockBB) return new BoundingBox();
|
|
250
255
|
const topLeft = blockBB.pt1.add(this.points[0]);
|
|
251
256
|
const bottomRight = blockBB.pt2.add(this.points[0]);
|
|
252
257
|
return new BoundingBox(topLeft, bottomRight);
|
package/core/entities/point.js
CHANGED
|
@@ -298,16 +298,16 @@ export class Point {
|
|
|
298
298
|
* Find the closest point on a arc between start and end points
|
|
299
299
|
* @param {Point} startPoint
|
|
300
300
|
* @param {Point} endPoint
|
|
301
|
-
* @param {Point}
|
|
301
|
+
* @param {Point} centrePoint
|
|
302
302
|
* @param {number} direction - CCW if > 0
|
|
303
303
|
* @return {Point} the closest point on the arc or null
|
|
304
304
|
*/
|
|
305
|
-
closestPointOnArc(startPoint, endPoint,
|
|
306
|
-
const length = this.distance(
|
|
307
|
-
const radius =
|
|
305
|
+
closestPointOnArc(startPoint, endPoint, centrePoint, direction = 0) {
|
|
306
|
+
const length = this.distance(centrePoint);
|
|
307
|
+
const radius = centrePoint.distance(startPoint);
|
|
308
308
|
|
|
309
|
-
const Cx =
|
|
310
|
-
const Cy =
|
|
309
|
+
const Cx = centrePoint.x + radius * (this.x - centrePoint.x) / length;
|
|
310
|
+
const Cy = centrePoint.y + radius * (this.y - centrePoint.y) / length;
|
|
311
311
|
const closest = new Point(Cx, Cy);
|
|
312
312
|
|
|
313
313
|
// circle
|
|
@@ -315,7 +315,7 @@ export class Point {
|
|
|
315
315
|
return closest;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
if (closest.isOnArc(startPoint, endPoint,
|
|
318
|
+
if (closest.isOnArc(startPoint, endPoint, centrePoint, direction)) {
|
|
319
319
|
return closest;
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -327,15 +327,15 @@ export class Point {
|
|
|
327
327
|
* Determine if point is on arc segment
|
|
328
328
|
* @param {Point} startPoint
|
|
329
329
|
* @param {Point} endPoint
|
|
330
|
-
* @param {Point}
|
|
330
|
+
* @param {Point} centrePoint
|
|
331
331
|
* @param {number} direction - CCW if > 0
|
|
332
332
|
* @return {Point} true or false
|
|
333
333
|
*/
|
|
334
|
-
isOnArc(startPoint, endPoint,
|
|
334
|
+
isOnArc(startPoint, endPoint, centrePoint, direction = 0) {
|
|
335
335
|
// direction: ccw arc > 0, clockwise arc <= 0
|
|
336
|
-
const snapAngle =
|
|
337
|
-
const startAngle =
|
|
338
|
-
const endAngle =
|
|
336
|
+
const snapAngle = centrePoint.angle(this);
|
|
337
|
+
const startAngle = centrePoint.angle(startPoint);
|
|
338
|
+
const endAngle = centrePoint.angle(endPoint);
|
|
339
339
|
|
|
340
340
|
if (direction > 0) {
|
|
341
341
|
// Counter Clockwise Arc
|
|
@@ -438,7 +438,7 @@ export class Point {
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
/**
|
|
441
|
-
* Returns apothem; the distance from arc
|
|
441
|
+
* Returns apothem; the distance from arc centre to cord midpoint
|
|
442
442
|
* @param {Point} nextPoint
|
|
443
443
|
* @return {number} apothem
|
|
444
444
|
*/
|
|
@@ -465,12 +465,12 @@ export class Point {
|
|
|
465
465
|
|
|
466
466
|
let a = this.apothem(nextPoint);
|
|
467
467
|
|
|
468
|
-
// check if the
|
|
468
|
+
// check if the centre point is inverted. i.e. at 180 it goes inside the arc
|
|
469
469
|
if (Math.abs(this.bulgeAngle()) > Math.PI) {
|
|
470
470
|
a = -a;
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
// get the direction from the midpoint to the
|
|
473
|
+
// get the direction from the midpoint to the centre point
|
|
474
474
|
const direction = this.angle(nextPoint) + (Math.PI / 2) * Math.sign(this.bulge);
|
|
475
475
|
// centre point is the apothem distance from the mid point in the given direction
|
|
476
476
|
const centre = midp.project(direction, a);
|
|
@@ -486,12 +486,12 @@ export class Point {
|
|
|
486
486
|
* @return {number} - the partial bulge value
|
|
487
487
|
*/
|
|
488
488
|
partialBulge(nextPoint, splitPoint, afterSplit = false) {
|
|
489
|
-
const
|
|
489
|
+
const centre = this.bulgeCentrePoint(nextPoint);
|
|
490
490
|
const direction = this.bulge > 0 ? 1 : -1;
|
|
491
491
|
|
|
492
|
-
const startAngle =
|
|
493
|
-
const endAngle =
|
|
494
|
-
const splitAngle =
|
|
492
|
+
const startAngle = centre.angle(this);
|
|
493
|
+
const endAngle = centre.angle(nextPoint);
|
|
494
|
+
const splitAngle = centre.angle(splitPoint);
|
|
495
495
|
|
|
496
496
|
let includedAngle;
|
|
497
497
|
if (afterSplit) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { DXFFile } from '../lib/dxf/dxfFile.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PolylineBase } from './polylineBase.js';
|
|
3
3
|
import { Point } from './point.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Polyline Entity Class
|
|
7
|
-
* @extends
|
|
7
|
+
* @extends PolylineBase
|
|
8
8
|
*/
|
|
9
|
-
export class Polyline extends
|
|
9
|
+
export class Polyline extends PolylineBase {
|
|
10
10
|
static type = 'Polyline';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -20,8 +20,8 @@ import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
|
20
20
|
* Base Polyline Entity Class
|
|
21
21
|
* @extends Entity
|
|
22
22
|
*/
|
|
23
|
-
export class
|
|
24
|
-
static type = '
|
|
23
|
+
export class PolylineBase extends Entity {
|
|
24
|
+
static type = 'PolylineBase';
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Create a Base Polyline
|
|
@@ -233,7 +233,7 @@ export class BasePolyline extends Entity {
|
|
|
233
233
|
/**
|
|
234
234
|
* Set entity points from a polyline point representation
|
|
235
235
|
* @param {Array} points
|
|
236
|
-
* @return {
|
|
236
|
+
* @return {PolylineBase}
|
|
237
237
|
*/
|
|
238
238
|
fromPolylinePoints(points) {
|
|
239
239
|
const pts = points.map((p) => new Point(p.x, p.y, p.bulge));
|
|
@@ -311,10 +311,10 @@ export class BasePolyline extends Entity {
|
|
|
311
311
|
let closestPoint;
|
|
312
312
|
let candidateSegment;
|
|
313
313
|
if (A.bulge !== 0) {
|
|
314
|
-
const
|
|
314
|
+
const centre = A.bulgeCentrePoint(B);
|
|
315
315
|
const direction = A.bulge > 0 ? 1 : -1;
|
|
316
|
-
closestPoint = P.closestPointOnArc(A, B,
|
|
317
|
-
candidateSegment = new Arc({ points: [
|
|
316
|
+
closestPoint = P.closestPointOnArc(A, B, centre, direction);
|
|
317
|
+
candidateSegment = new Arc({ points: [centre, A, B], direction });
|
|
318
318
|
} else {
|
|
319
319
|
closestPoint = P.closestPointOnLine(A, B);
|
|
320
320
|
candidateSegment = new Line({ points: [A, B] });
|
|
@@ -420,9 +420,9 @@ export class BasePolyline extends Entity {
|
|
|
420
420
|
*/
|
|
421
421
|
isPointOnSegment(point, A, B) {
|
|
422
422
|
if (A.bulge !== 0) {
|
|
423
|
-
const
|
|
423
|
+
const centre = A.bulgeCentrePoint(B);
|
|
424
424
|
const direction = A.bulge > 0 ? 1 : -1;
|
|
425
|
-
return point.isOnArc(A, B,
|
|
425
|
+
return point.isOnArc(A, B, centre, direction);
|
|
426
426
|
}
|
|
427
427
|
return point.isOnLine(A, B);
|
|
428
428
|
}
|
|
@@ -438,9 +438,9 @@ export class BasePolyline extends Entity {
|
|
|
438
438
|
const B = this.points[segmentIndex % this.points.length];
|
|
439
439
|
|
|
440
440
|
if (A.bulge !== 0) {
|
|
441
|
-
const
|
|
441
|
+
const centre = A.bulgeCentrePoint(B);
|
|
442
442
|
const direction = A.bulge > 0 ? 1 : -1;
|
|
443
|
-
const closest = point.closestPointOnArc(A, B,
|
|
443
|
+
const closest = point.closestPointOnArc(A, B, centre, direction);
|
|
444
444
|
|
|
445
445
|
if (closest) {
|
|
446
446
|
return closest;
|
|
@@ -464,10 +464,10 @@ export class BasePolyline extends Entity {
|
|
|
464
464
|
const B = this.points[segmentIndex % this.points.length];
|
|
465
465
|
|
|
466
466
|
if (A.bulge !== 0) {
|
|
467
|
-
const
|
|
467
|
+
const centre = A.bulgeCentrePoint(B);
|
|
468
468
|
const direction = A.bulge > 0 ? 1 : -1;
|
|
469
|
-
const startAngle =
|
|
470
|
-
const pointAngle =
|
|
469
|
+
const startAngle = centre.angle(A);
|
|
470
|
+
const pointAngle = centre.angle(point);
|
|
471
471
|
return ((pointAngle - startAngle) * direction + 4 * Math.PI) % (2 * Math.PI);
|
|
472
472
|
}
|
|
473
473
|
|
|
@@ -490,8 +490,8 @@ export class BasePolyline extends Entity {
|
|
|
490
490
|
let boundingBox = BoundingBox.lineBoundingBox(this.points[i], nextPoint);
|
|
491
491
|
|
|
492
492
|
if (this.points[i].bulge !== 0) {
|
|
493
|
-
const
|
|
494
|
-
boundingBox = BoundingBox.arcBoundingBox(
|
|
493
|
+
const centrePoint = this.points[i].bulgeCentrePoint(nextPoint);
|
|
494
|
+
boundingBox = BoundingBox.arcBoundingBox(centrePoint, this.points[i], nextPoint, this.points[i].bulge);
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
xmin = Math.min(xmin, boundingBox.xMin);
|
|
@@ -519,9 +519,9 @@ export class BasePolyline extends Entity {
|
|
|
519
519
|
// get the angle at the end of the previous segment
|
|
520
520
|
let lastSegAngle;
|
|
521
521
|
if (lastSegIsArc) {
|
|
522
|
-
const
|
|
523
|
-
// angle is perpendicular to the
|
|
524
|
-
lastSegAngle =
|
|
522
|
+
const centrePoint = this.points.at(-2).bulgeCentrePoint(this.points.at(-1));
|
|
523
|
+
// angle is perpendicular to the centre-to-end point ray, in the direction of the bulge
|
|
524
|
+
lastSegAngle = centrePoint.angle(this.points.at(-1)) + (Math.PI / 2) * Math.sign(lastSegBulge);
|
|
525
525
|
} else {
|
|
526
526
|
// line segment angle is the angle from point 0 to point 1
|
|
527
527
|
lastSegAngle = this.points.at(-2).angle(this.points.at(-1));
|