@design-core/core 0.0.5 → 0.0.7
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/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +33 -284
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- 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/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- 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 +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +36 -13
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- 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 +27 -5
- package/core/lib/selectionManager.js +2 -2
- 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 +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +129 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- 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/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- 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/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- 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 +3 -3
- 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 +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +296 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
package/core/entities/hatch.js
CHANGED
|
@@ -34,50 +34,45 @@ export class Hatch extends Entity {
|
|
|
34
34
|
constructor(data) {
|
|
35
35
|
super(data);
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
Object.defineProperty(this, '
|
|
39
|
-
value:
|
|
37
|
+
// cache for pre-computed world-space pattern line segments
|
|
38
|
+
Object.defineProperty(this, 'cachedPattern', {
|
|
39
|
+
value: null,
|
|
40
40
|
writable: true,
|
|
41
|
+
enumerable: false,
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Boundary shapes stored in EntityProperties so all entity data lives in one place.
|
|
45
|
+
this.properties.add(Property.Names.CHILDENTITIES, {
|
|
46
|
+
type: Property.Type.ENTITIES,
|
|
47
|
+
value: [],
|
|
48
|
+
visible: false,
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
writable: true,
|
|
51
|
-
});
|
|
51
|
+
// lineType not applicable to hatch
|
|
52
|
+
this.properties.remove(Property.Names.LINETYPE);
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
enumerable: true,
|
|
57
|
-
get: this.getPatternName,
|
|
58
|
-
set: this.setPatternName,
|
|
59
|
-
});
|
|
54
|
+
const rawPatternName = String(Property.loadValue([data?.[2], data?.patternName], 'ANSI31')).toUpperCase();
|
|
55
|
+
// DXF Groupcode 70 - Solid Fill Flag (1 = solid, 0 = pattern): if set, override pattern name
|
|
56
|
+
const resolvedPatternName = data?.[70] === 1 ? 'SOLID' : rawPatternName;
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Object.defineProperty(this, 'scale', {
|
|
68
|
-
value: 1,
|
|
69
|
-
writable: true,
|
|
70
|
-
enumerable: true,
|
|
58
|
+
// DXF Groupcode 2 - Hatch pattern name
|
|
59
|
+
this.properties.add(Property.Names.PATTERNNAME, {
|
|
60
|
+
type: Property.Type.LIST,
|
|
61
|
+
value: resolvedPatternName,
|
|
62
|
+
dxfCode: 2,
|
|
63
|
+
options: () => Object.keys(Patterns.hatch_patterns).map((name) => ({ display: name, value: name })),
|
|
71
64
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
// DXF Groupcode 41 - Hatch pattern scale
|
|
66
|
+
this.properties.add(Property.Names.SCALE, {
|
|
67
|
+
type: Property.Type.NUMBER,
|
|
68
|
+
value: Property.loadValue([data?.[41], data?.scale], 1),
|
|
69
|
+
dxfCode: 41,
|
|
77
70
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
// DXF Groupcode 52 - Hatch pattern angle
|
|
72
|
+
this.properties.add(Property.Names.ANGLE, {
|
|
73
|
+
type: Property.Type.NUMBER,
|
|
74
|
+
value: Property.loadValue([data?.[52], data?.angle], 0),
|
|
75
|
+
dxfCode: 52,
|
|
81
76
|
});
|
|
82
77
|
|
|
83
78
|
// add a single point to this.points if no other points exist
|
|
@@ -85,24 +80,15 @@ export class Hatch extends Entity {
|
|
|
85
80
|
this.points.push(new Point());
|
|
86
81
|
}
|
|
87
82
|
|
|
88
|
-
// DXF Groupcode 70 - Solid Fill Flag (1 = solid, 0 = pattern)
|
|
89
|
-
this.solid = Boolean(Property.loadValue([data?.solid, data?.[70]], 0));
|
|
90
|
-
// DXF Groupcode 2 - Hatch pattern name
|
|
91
|
-
this.patternName = Property.loadValue([data?.patternName, data?.[2]], 'ANSI31');
|
|
92
|
-
// DXF Groupcode 41 - Hatch pattern scale
|
|
93
|
-
this.scale = Property.loadValue([data?.scale, data?.[41]], 1);
|
|
94
|
-
// DXF Groupcode 52 - Hatch pattern angle
|
|
95
|
-
this.angle = Property.loadValue([data?.angle, data?.[52]], 0);
|
|
96
|
-
|
|
97
83
|
if (data) {
|
|
98
84
|
if (data.hasOwnProperty('childEntities')) {
|
|
99
85
|
if (Array.isArray(data.childEntities)) {
|
|
100
|
-
this.
|
|
86
|
+
this.setProperty(Property.Names.CHILDENTITIES, data.childEntities);
|
|
101
87
|
}
|
|
102
88
|
} else {
|
|
103
89
|
const shapes = this.processBoundaryData(data);
|
|
104
90
|
if (shapes.length) {
|
|
105
|
-
this.
|
|
91
|
+
this.setProperty(Property.Names.CHILDENTITIES, shapes);
|
|
106
92
|
}
|
|
107
93
|
}
|
|
108
94
|
}
|
|
@@ -118,20 +104,198 @@ export class Hatch extends Entity {
|
|
|
118
104
|
}
|
|
119
105
|
|
|
120
106
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
107
|
+
* Build and cache world-space line segments for the current pattern.
|
|
108
|
+
* For pattern hatches, segments are pre-clipped against the boundary using
|
|
109
|
+
* geometric intersection so draw() requires no ctx.save/clip/restore overhead.
|
|
123
110
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
111
|
+
buildPatternCache() {
|
|
112
|
+
if (!this.getProperty(Property.Names.CHILDENTITIES).length) {
|
|
113
|
+
this.cachedPattern = [];
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
if (this.getProperty(Property.Names.PATTERNNAME) === 'SOLID' || !Patterns.patternExists(this.getProperty(Property.Names.PATTERNNAME))) {
|
|
118
|
+
this.cachedPattern = [];
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build closed boundary arrays for intersection-based pre-clipping
|
|
123
|
+
const bb = this.boundingBox();
|
|
124
|
+
const boundaryExtentX = bb.xMax + bb.xLength + 1;
|
|
125
|
+
const boundaries = [];
|
|
126
|
+
// Collect all interior boundary vertices (the junction points between segments).
|
|
127
|
+
// An intersection point that lands exactly on a junction vertex will be reported
|
|
128
|
+
// by both adjacent segments, doubling the crossing count unless we deduplicate.
|
|
129
|
+
const boundaryVertices = [];
|
|
130
|
+
const childEntities = this.getProperty(Property.Names.CHILDENTITIES);
|
|
131
|
+
for (let i = 0; i < childEntities.length; i++) {
|
|
132
|
+
const shape = childEntities[i];
|
|
133
|
+
if (!shape.points.length) continue;
|
|
134
|
+
const pts = [...shape.points];
|
|
135
|
+
if (!pts[0].isSame(pts.at(-1))) pts.push(pts[0]);
|
|
136
|
+
boundaries.push(pts);
|
|
137
|
+
// All unique vertices (skip only the closure duplicate at pts[length-1]).
|
|
138
|
+
// pts[0] is also a junction — shared by the last and first segments.
|
|
139
|
+
for (let j = 0; j < pts.length - 1; j++) {
|
|
140
|
+
boundaryVertices.push(pts[j]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const centerPoint = bb.centerPoint;
|
|
145
|
+
const bbXLength = bb.xLength;
|
|
146
|
+
const bbYLength = bb.yLength;
|
|
147
|
+
const s = this.getProperty(Property.Names.SCALE);
|
|
148
|
+
const lines = [];
|
|
149
|
+
|
|
150
|
+
const pattern = Patterns.getPattern(this.getProperty(Property.Names.PATTERNNAME));
|
|
151
|
+
pattern.forEach((patternLine) => {
|
|
152
|
+
let dashLength = bbXLength / 2;
|
|
153
|
+
if (patternLine.dashes.length) {
|
|
154
|
+
dashLength = patternLine.getDashLength();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let dashes = [...patternLine.dashes];
|
|
158
|
+
let dashOffset = 0;
|
|
159
|
+
|
|
160
|
+
if (dashes.length && dashes[0] < 0) {
|
|
161
|
+
const firstIndex = dashes.shift();
|
|
162
|
+
dashOffset = Math.abs(firstIndex);
|
|
163
|
+
dashes.push(firstIndex);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
dashes = dashes.map((x) => (Math.abs(x) + 0.00001) * s);
|
|
167
|
+
|
|
168
|
+
// Combined rotation: pattern line angle plus the hatch entity angle
|
|
169
|
+
const rotation = Utils.degrees2radians(patternLine.angle + this.getProperty(Property.Names.ANGLE));
|
|
170
|
+
|
|
171
|
+
// Precomputed rotation components used to transform between pattern-local
|
|
172
|
+
// (line-aligned) space and world space throughout this family's loop.
|
|
173
|
+
const cosR = Math.cos(rotation);
|
|
174
|
+
const sinR = Math.sin(rotation);
|
|
175
|
+
|
|
176
|
+
// World-space anchor for this family: bounding-box centre shifted by the
|
|
177
|
+
// pattern line's origin offset (scaled to match the hatch scale).
|
|
178
|
+
const cx = centerPoint.x + patternLine.xOrigin * s;
|
|
179
|
+
const cy = centerPoint.y + patternLine.yOrigin * s;
|
|
180
|
+
|
|
181
|
+
// Bounding-box half-extents in pattern-local (unscaled) space.
|
|
182
|
+
const halfW = bbXLength / (2 * s);
|
|
183
|
+
const halfH = bbYLength / (2 * s);
|
|
184
|
+
|
|
185
|
+
// Absolute trig values for projecting axis-aligned extents onto rotated axes.
|
|
186
|
+
const sinA = Math.abs(sinR);
|
|
187
|
+
const cosA = Math.abs(cosR);
|
|
188
|
+
|
|
189
|
+
// Half-extents of the bounding box projected onto the pattern line's
|
|
190
|
+
// parallel (halfX) and perpendicular (halfY) axes. halfX bounds how far
|
|
191
|
+
// each raw segment must reach; halfY bounds how many parallel lines are needed.
|
|
192
|
+
const halfX = halfW * cosA + halfH * sinA;
|
|
193
|
+
const halfY = halfW * sinA + halfH * cosA;
|
|
194
|
+
|
|
195
|
+
// Perpendicular projection of the family's origin offset from the bounding-box
|
|
196
|
+
// centre onto the direction perpendicular to the pattern lines. A non-zero
|
|
197
|
+
// origin shifts the whole family, so the loop range must be widened on the far
|
|
198
|
+
// side and the tangent guard must use the shifted position.
|
|
199
|
+
const originPerpOffset = patternLine.yOrigin * cosR - patternLine.xOrigin * sinR;
|
|
200
|
+
|
|
201
|
+
const maxSegmentsPerFamily = 1000;
|
|
202
|
+
const yIncrement = Math.min(
|
|
203
|
+
Math.ceil((halfY + Math.abs(originPerpOffset)) / Math.abs(patternLine.yDelta)),
|
|
204
|
+
maxSegmentsPerFamily,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const segments = [];
|
|
208
|
+
for (let i = -yIncrement; i < yIncrement; i++) {
|
|
209
|
+
const yOffset = patternLine.yDelta * i;
|
|
210
|
+
// A line whose shifted perpendicular position equals or exceeds halfY is
|
|
211
|
+
// tangent to or outside the bounding box — it cannot produce any valid
|
|
212
|
+
// inside sub-segment. Skip it to avoid phantom slivers caused by
|
|
213
|
+
// near-tangent numerical artefacts in the arc-intersection code.
|
|
214
|
+
if (Math.abs(yOffset + originPerpOffset) >= halfY) continue;
|
|
215
|
+
// Each family member i is phase-shifted along the line direction by i × xDelta.
|
|
216
|
+
const xPhaseShift = patternLine.xDelta * i;
|
|
217
|
+
// Extend the raw segment far enough in both directions to cover the bounding
|
|
218
|
+
// box regardless of how large the phase shift grows for large i.
|
|
219
|
+
const xHalfSpan = dashLength * Math.ceil((halfX + Math.abs(xPhaseShift)) / dashLength);
|
|
220
|
+
const lx1 = xPhaseShift - xHalfSpan + dashOffset;
|
|
221
|
+
const lx2 = xPhaseShift + xHalfSpan;
|
|
222
|
+
const ly = yOffset;
|
|
223
|
+
const rx1 = (lx1 * cosR - ly * sinR) * s + cx;
|
|
224
|
+
const ry1 = (lx1 * sinR + ly * cosR) * s + cy;
|
|
225
|
+
const rx2 = (lx2 * cosR - ly * sinR) * s + cx;
|
|
226
|
+
const ry2 = (lx2 * sinR + ly * cosR) * s + cy;
|
|
227
|
+
|
|
228
|
+
// Find all intersection t-values where this segment crosses a boundary edge.
|
|
229
|
+
// A point that falls on a boundary vertex is shared by two adjacent segments
|
|
230
|
+
// and would be reported twice, doubling the crossing count. Track which vertex
|
|
231
|
+
// t-values have already been added and skip duplicates.
|
|
232
|
+
const segStartPt = new Point(rx1, ry1);
|
|
233
|
+
const segEndPt = new Point(rx2, ry2);
|
|
234
|
+
const dir = segEndPt.subtract(segStartPt);
|
|
235
|
+
const lenSq = dir.dot(dir);
|
|
236
|
+
if (lenSq === 0) continue;
|
|
237
|
+
const sqrtLenSq = Math.sqrt(lenSq);
|
|
238
|
+
const ts = [0, 1];
|
|
239
|
+
const vertexTsAdded = new Set();
|
|
240
|
+
for (const b of boundaries) {
|
|
241
|
+
const result = Intersection.intersectPolylinePolyline(b, [segStartPt, segEndPt]);
|
|
242
|
+
for (const pt of result.points) {
|
|
243
|
+
const t = pt.subtract(segStartPt).dot(dir) / lenSq;
|
|
244
|
+
if (t > 0.0001 && t < 0.9999) {
|
|
245
|
+
// Check if this point coincides with a boundary vertex
|
|
246
|
+
let isVertex = false;
|
|
247
|
+
for (const v of boundaryVertices) {
|
|
248
|
+
const vdx = pt.x - v.x;
|
|
249
|
+
const vdy = pt.y - v.y;
|
|
250
|
+
if (vdx * vdx + vdy * vdy < 1e-10) {
|
|
251
|
+
isVertex = true;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (isVertex) {
|
|
256
|
+
// Round t to 6 decimal places to get a stable key
|
|
257
|
+
const tKey = Math.round(t * 1e6);
|
|
258
|
+
if (!vertexTsAdded.has(tKey)) {
|
|
259
|
+
vertexTsAdded.add(tKey);
|
|
260
|
+
ts.push(t);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
ts.push(t);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
ts.sort((a, b) => a - b);
|
|
270
|
+
|
|
271
|
+
// Deduplicate very close t-values (e.g. a corner vertex found by two edges)
|
|
272
|
+
const uniqueTs = [ts[0]];
|
|
273
|
+
for (let k = 1; k < ts.length; k++) {
|
|
274
|
+
if (ts[k] - uniqueTs.at(-1) > 0.0001) uniqueTs.push(ts[k]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Test each interval's midpoint; keep sub-segments that are inside the boundary
|
|
278
|
+
for (let k = 0; k < uniqueTs.length - 1; k++) {
|
|
279
|
+
const tMid = (uniqueTs[k] + uniqueTs[k + 1]) / 2;
|
|
280
|
+
if (Intersection.isInsidePolyline(segStartPt.x + tMid * dir.x, segStartPt.y + tMid * dir.y, boundaries, boundaryExtentX)) {
|
|
281
|
+
const p1 = segStartPt.lerp(segEndPt, uniqueTs[k]);
|
|
282
|
+
const p2 = segStartPt.lerp(segEndPt, uniqueTs[k + 1]);
|
|
283
|
+
// Store the segment along with its initial dash phase, which is used by draw()
|
|
284
|
+
segments.push({
|
|
285
|
+
x1: p1.x,
|
|
286
|
+
y1: p1.y,
|
|
287
|
+
x2: p2.x,
|
|
288
|
+
y2: p2.y,
|
|
289
|
+
dashPhase: uniqueTs[k] * sqrtLenSq,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
lines.push({ dashes, segments });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
this.cachedPattern = lines;
|
|
135
299
|
}
|
|
136
300
|
|
|
137
301
|
/**
|
|
@@ -189,6 +353,16 @@ export class Hatch extends Entity {
|
|
|
189
353
|
}
|
|
190
354
|
|
|
191
355
|
shape.points.push(polyPoint);
|
|
356
|
+
|
|
357
|
+
// On the last vertex, apply the closed flag (73) to the shape.
|
|
358
|
+
// For polyline boundaries 73 = is-closed and appears once, before
|
|
359
|
+
// the vertices, so it has already been parsed into data[73]. We
|
|
360
|
+
// must not consume it inside the arc-edge branch (that branch uses
|
|
361
|
+
// 73 as the per-edge "is counter-clockwise" flag), so we read it
|
|
362
|
+
// here only when we are in the polyline branch and on the final vertex.
|
|
363
|
+
if (edgeNum === edgeCount - 1 && this.getDataValue(data, 73)) {
|
|
364
|
+
shape.flags.addValue(1);
|
|
365
|
+
}
|
|
192
366
|
} else if (edgeType === 1) {
|
|
193
367
|
// Line
|
|
194
368
|
// 10 - X; 20 - Y; 11 - X; 21 - Y
|
|
@@ -223,7 +397,7 @@ export class Hatch extends Entity {
|
|
|
223
397
|
throw Error(err);
|
|
224
398
|
}
|
|
225
399
|
shapeData.points.push(centerPoint);
|
|
226
|
-
shapeData
|
|
400
|
+
shapeData.radius = this.getDataValue(data, 40);
|
|
227
401
|
shapeData.startAngle = this.getDataValue(data, 50);
|
|
228
402
|
shapeData.endAngle = this.getDataValue(data, 51);
|
|
229
403
|
// arc direction: - ccw > 0, cw <= 0 default 1
|
|
@@ -312,7 +486,7 @@ export class Hatch extends Entity {
|
|
|
312
486
|
const boundary = this.processSelection(selectedItems);
|
|
313
487
|
|
|
314
488
|
if (boundary.length) {
|
|
315
|
-
this.
|
|
489
|
+
this.setProperty(Property.Names.CHILDENTITIES, boundary);
|
|
316
490
|
validBoundary = true;
|
|
317
491
|
} else {
|
|
318
492
|
// reset selection
|
|
@@ -335,7 +509,7 @@ export class Hatch extends Entity {
|
|
|
335
509
|
const selectedItems = DesignCore.Scene.selectionManager.selectedItems.slice(0);
|
|
336
510
|
const shapes = this.processSelection(selectedItems);
|
|
337
511
|
if (shapes.length) {
|
|
338
|
-
DesignCore.Scene.
|
|
512
|
+
DesignCore.Scene.previewEntities.create(this.type, { points: this.points, childEntities: shapes }); // childEntities is passed as plain data here, not via getProperty
|
|
339
513
|
}
|
|
340
514
|
}
|
|
341
515
|
|
|
@@ -409,6 +583,8 @@ export class Hatch extends Entity {
|
|
|
409
583
|
// Drop the redundant closure point — the first point already records
|
|
410
584
|
// the same position; keeping it would create a zero-length segment
|
|
411
585
|
shape.points.push(...iterationPoints.slice(0, -1));
|
|
586
|
+
// Mark as closed (flag bit 1)
|
|
587
|
+
shape.flags.addValue(1);
|
|
412
588
|
selectedchildEntities.push(shape);
|
|
413
589
|
iterationPoints = [];
|
|
414
590
|
break;
|
|
@@ -422,168 +598,52 @@ export class Hatch extends Entity {
|
|
|
422
598
|
|
|
423
599
|
/**
|
|
424
600
|
* Draw the entity
|
|
425
|
-
* @param {Object}
|
|
601
|
+
* @param {Object} renderer
|
|
426
602
|
* @param {number} scale
|
|
427
603
|
*/
|
|
428
|
-
draw(
|
|
604
|
+
draw(renderer) {
|
|
429
605
|
// ensure the scale is valid
|
|
430
|
-
if (this.
|
|
431
|
-
this.
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (!this.childEntities.length) return;
|
|
435
|
-
|
|
436
|
-
ctx.save();
|
|
437
|
-
|
|
438
|
-
// Start a new composite path for all boundary loops
|
|
439
|
-
try {
|
|
440
|
-
ctx.beginPath();
|
|
441
|
-
} catch { // Cairo
|
|
442
|
-
ctx.newPath();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Trace all boundary paths as sub-paths in a single composite path
|
|
446
|
-
// Using even-odd fill rule, islands are automatically handled:
|
|
447
|
-
// areas enclosed by an odd number of boundaries are filled,
|
|
448
|
-
// areas enclosed by an even number are left empty
|
|
449
|
-
for (let i = 0; i < this.childEntities.length; i++) {
|
|
450
|
-
const shape = this.childEntities[i];
|
|
451
|
-
if (!shape.points.length) continue;
|
|
452
|
-
|
|
453
|
-
// Start a new sub-path for this boundary
|
|
454
|
-
ctx.moveTo(shape.points[0].x, shape.points[0].y);
|
|
455
|
-
// Trace the boundary path (without stroking)
|
|
456
|
-
shape.draw(ctx, scale, false);
|
|
457
|
-
// Close the sub-path
|
|
458
|
-
ctx.closePath();
|
|
606
|
+
if (this.getProperty(Property.Names.SCALE) < 0.01) {
|
|
607
|
+
this.setProperty(Property.Names.SCALE, 1);
|
|
459
608
|
}
|
|
460
609
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
try {
|
|
479
|
-
ctx.rect(bb.xMin - bb.xLength, bb.yMin - bb.yLength, bb.xLength * 3, bb.yLength * 3);
|
|
480
|
-
} catch { // Cairo
|
|
481
|
-
ctx.rectangle(bb.xMin - bb.xLength, bb.yMin - bb.yLength, bb.xLength * 3, bb.yLength * 3);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Draw the pattern/fill once over the composite clipped region
|
|
485
|
-
this.createPattern(ctx, scale, bb);
|
|
486
|
-
|
|
487
|
-
ctx.restore();
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Draw the hatch pattern to the context
|
|
492
|
-
* @param {Object} ctx
|
|
493
|
-
* @param {number} scale
|
|
494
|
-
* @param {BoundingBox} boundingbox - bounding box of the hatch boundaries, used to size the pattern
|
|
495
|
-
*/
|
|
496
|
-
createPattern(ctx, scale, boundingbox) {
|
|
497
|
-
if (!Patterns.patternExists(this.patternName) || this.solid) {
|
|
498
|
-
ctx.fill();
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const boundingBox = boundingbox;
|
|
503
|
-
const centerPoint = boundingBox.centerPoint;
|
|
504
|
-
const bbXLength = boundingBox.xLength;
|
|
505
|
-
const bbYLength = boundingBox.yLength;
|
|
506
|
-
|
|
507
|
-
const pattern = Patterns.getPattern(this.patternName);
|
|
508
|
-
pattern.forEach((patternLine) => {
|
|
509
|
-
// Each pattern line is considered to be the first member of a line family,
|
|
510
|
-
// Patterns are created by applying the delta offsets in both directions to generate an infinite family of parallel lines.
|
|
511
|
-
// originX and originY values are the offsets of the line from the origin and are applied without rotation
|
|
512
|
-
// The delta-x value indicates the displacement between members of the family in the direction of the line. It is used only for dashed lines.
|
|
513
|
-
// The delta-y value indicates the spacing between members of the family; that is, it is measured perpendicular to the lines.
|
|
514
|
-
// A line is considered to be of infinite length.
|
|
515
|
-
// A dash pattern is superimposed on the line.
|
|
516
|
-
|
|
517
|
-
// define dashlength - i.e. the length of each dash summed
|
|
518
|
-
// where there is no dash defined use half the boundingbox xlength
|
|
519
|
-
let dashLength = bbXLength / 2;
|
|
520
|
-
if (patternLine.dashes.length) {
|
|
521
|
-
dashLength = patternLine.getDashLength();
|
|
610
|
+
const childEntitiesDraw = this.getProperty(Property.Names.CHILDENTITIES);
|
|
611
|
+
if (!childEntitiesDraw.length) return;
|
|
612
|
+
|
|
613
|
+
// Build cache if stale (cachedPattern === null means never built or invalidated)
|
|
614
|
+
if (this.cachedPattern === null) this.buildPatternCache();
|
|
615
|
+
|
|
616
|
+
if (this.getProperty(Property.Names.PATTERNNAME) === 'SOLID') {
|
|
617
|
+
// Design renders solid hatches with a direct fill() call rather than the
|
|
618
|
+
// dense cross-hatch line pattern used by commercial CAD applications
|
|
619
|
+
// (Commercial CAD's SOLID pattern uses two line families at 0.0001-unit spacing).
|
|
620
|
+
// fill() is resolution-independent, handles curved boundaries exactly,
|
|
621
|
+
// and avoids the cost of generating and clipping thousands of segments.
|
|
622
|
+
renderer.beginPath();
|
|
623
|
+
for (const shape of childEntitiesDraw) {
|
|
624
|
+
if (!shape.points.length) continue;
|
|
625
|
+
renderer.tracePath(shape.toPolylinePoints());
|
|
626
|
+
renderer.closePath();
|
|
522
627
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
// if the first dash is negative move it to the end
|
|
530
|
-
const firstIndex = dashes.shift();
|
|
531
|
-
dashOffset = Math.abs(firstIndex);
|
|
532
|
-
dashes.push(firstIndex);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
dashes = dashes.map((x) => Math.abs(x) + 0.00001);
|
|
536
|
-
|
|
537
|
-
try {
|
|
538
|
-
ctx.setLineDash(dashes, 0);
|
|
539
|
-
ctx.lineWidth = (1 / scale / this.scale);
|
|
540
|
-
ctx.beginPath();
|
|
541
|
-
} catch { // Cairo
|
|
542
|
-
ctx.setDash(dashes, 0);
|
|
543
|
-
ctx.setLineWidth(1 / scale / this.scale);
|
|
544
|
-
ctx.newPath();
|
|
628
|
+
renderer.applyPath({ fill: true, stroke: false, fillRule: 'evenodd' });
|
|
629
|
+
} else {
|
|
630
|
+
// Pattern hatch: draw pre-clipped segments directly.
|
|
631
|
+
for (const family of this.cachedPattern) {
|
|
632
|
+
renderer.setDash([], 0);
|
|
633
|
+
renderer.drawSegments(family.segments, family.dashes);
|
|
545
634
|
}
|
|
635
|
+
}
|
|
546
636
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
// Half-extent along the line direction (X in local frame)
|
|
557
|
-
const halfX = halfW * cosR + halfH * sinR;
|
|
558
|
-
// Half-extent perpendicular to lines (Y in local frame)
|
|
559
|
-
const halfY = halfW * sinR + halfH * cosR;
|
|
560
|
-
|
|
561
|
-
const xIncrement = Math.ceil(halfX / dashLength);
|
|
562
|
-
const yIncrement = Math.ceil(halfY / Math.abs(patternLine.yDelta));
|
|
563
|
-
|
|
564
|
-
// Set transform once for all lines in this pattern line family
|
|
565
|
-
ctx.save();
|
|
566
|
-
// translate to the center of the shape
|
|
567
|
-
// apply the origin offsets without rotation
|
|
568
|
-
ctx.translate(centerPoint.x + patternLine.xOrigin * this.scale, centerPoint.y + patternLine.yOrigin * this.scale);
|
|
569
|
-
// scale the context
|
|
570
|
-
ctx.scale(this.scale, this.scale);
|
|
571
|
-
// rotate the context
|
|
572
|
-
ctx.rotate(rotation);
|
|
573
|
-
|
|
574
|
-
for (let i = -yIncrement; i < yIncrement; i++) {
|
|
575
|
-
// apply the deltaX offset for odd iterations
|
|
576
|
-
// define offsets for the current iteration
|
|
577
|
-
const xOffset = patternLine.xDelta * Math.abs(i) + dashLength * xIncrement;
|
|
578
|
-
const yOffset = patternLine.yDelta * i;
|
|
579
|
-
|
|
580
|
-
ctx.moveTo(-xOffset + dashOffset, yOffset);
|
|
581
|
-
ctx.lineTo(xOffset, yOffset);
|
|
637
|
+
// Draw the boundary outline when hovered or selected.
|
|
638
|
+
const isSelected = DesignCore.Scene.selectionManager.selectedItems.includes(this);
|
|
639
|
+
const isHovered = DesignCore.Scene.hoverEntities.indexOf(this) !== -1;
|
|
640
|
+
if (isSelected || isHovered) {
|
|
641
|
+
renderer.setDash([], 0);
|
|
642
|
+
for (const shape of childEntitiesDraw) {
|
|
643
|
+
if (!shape.points.length) continue;
|
|
644
|
+
renderer.drawShape(shape.toPolylinePoints(), { closed: true });
|
|
582
645
|
}
|
|
583
|
-
|
|
584
|
-
ctx.stroke();
|
|
585
|
-
ctx.restore();
|
|
586
|
-
});
|
|
646
|
+
}
|
|
587
647
|
}
|
|
588
648
|
|
|
589
649
|
/**
|
|
@@ -592,14 +652,15 @@ export class Hatch extends Entity {
|
|
|
592
652
|
*/
|
|
593
653
|
dxf(file) {
|
|
594
654
|
// skip if no boundary shapes
|
|
595
|
-
|
|
655
|
+
const childEntitiesDxf = this.getProperty(Property.Names.CHILDENTITIES);
|
|
656
|
+
if (!childEntitiesDxf.length) {
|
|
596
657
|
return;
|
|
597
658
|
}
|
|
598
659
|
|
|
599
660
|
file.writeGroupCode('0', 'HATCH');
|
|
600
|
-
file.writeGroupCode('5', this.
|
|
661
|
+
file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
|
|
601
662
|
file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
|
|
602
|
-
file.writeGroupCode('8', this.
|
|
663
|
+
file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
|
|
603
664
|
file.writeGroupCode('100', 'AcDbHatch', DXFFile.Version.R2000);
|
|
604
665
|
file.writeGroupCode('10', '0.0');
|
|
605
666
|
file.writeGroupCode('20', '0.0');
|
|
@@ -609,15 +670,15 @@ export class Hatch extends Entity {
|
|
|
609
670
|
file.writeGroupCode('220', '0.0'); // Extrusion Direction Y
|
|
610
671
|
file.writeGroupCode('230', '1.0'); // Extrusion Direction Z
|
|
611
672
|
|
|
612
|
-
file.writeGroupCode('2', this.
|
|
613
|
-
file.writeGroupCode('70', this.
|
|
673
|
+
file.writeGroupCode('2', this.getProperty(Property.Names.PATTERNNAME)); // Hatch pattern name
|
|
674
|
+
file.writeGroupCode('70', this.getProperty(Property.Names.PATTERNNAME) === 'SOLID' ? 1 : 0); // Solid Fill Flag (1 = solid, 0 = pattern)
|
|
614
675
|
file.writeGroupCode('71', '0'); // Associativity flag (associative = 1; non-associative = 0); for MPolygon, solid-fill flag (has solidfill = 1; lacks solid fill = 0)
|
|
615
676
|
|
|
616
677
|
|
|
617
|
-
file.writeGroupCode('91',
|
|
678
|
+
file.writeGroupCode('91', childEntitiesDxf.length); // Number of boundary path loops
|
|
618
679
|
|
|
619
|
-
for (let i = 0; i <
|
|
620
|
-
const shape =
|
|
680
|
+
for (let i = 0; i < childEntitiesDxf.length; i++) {
|
|
681
|
+
const shape = childEntitiesDxf[i];
|
|
621
682
|
file.writeGroupCode('92', '7'); // Boundary path type flag (bit coded): 0 = Default; 1 = External; 2 = Polyline 4 = Derived; 8 = Textbox; 16 = Outermost
|
|
622
683
|
file.writeGroupCode('72', '1'); // Edge type (only if boundary is not a polyline): 1 = Line; 2 = Circular arc; 3 = Elliptic arc; 4 = Spline
|
|
623
684
|
file.writeGroupCode('73', '1'); // For MPolygon, boundary annotation flag (boundary is an annotated boundary = 1; boundary is not an annotated boundary = 0)
|
|
@@ -635,25 +696,25 @@ export class Hatch extends Entity {
|
|
|
635
696
|
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)
|
|
636
697
|
file.writeGroupCode('76', '1'); // Hatch pattern type: 0 = User-defined; 1 = Predefined; 2 = Custom
|
|
637
698
|
|
|
638
|
-
if (
|
|
639
|
-
file.writeGroupCode('52', this.
|
|
640
|
-
file.writeGroupCode('41', this.
|
|
699
|
+
if (this.getProperty(Property.Names.PATTERNNAME) !== 'SOLID') {
|
|
700
|
+
file.writeGroupCode('52', this.getProperty(Property.Names.ANGLE)); // Hatch Pattern angle
|
|
701
|
+
file.writeGroupCode('41', this.getProperty(Property.Names.SCALE)); // Hatch Pattern scale
|
|
641
702
|
file.writeGroupCode('77', '0'); // Hatch pattern double flag(pattern fill only): 0 = not double; 1 = double
|
|
642
|
-
file.writeGroupCode('78', Patterns.getPatternLineCount(this.
|
|
703
|
+
file.writeGroupCode('78', Patterns.getPatternLineCount(this.getProperty(Property.Names.PATTERNNAME))); // Number of pattern definition lines
|
|
643
704
|
|
|
644
705
|
// Pattern data
|
|
645
|
-
const pattern = Patterns.getPattern(this.
|
|
706
|
+
const pattern = Patterns.getPattern(this.getProperty(Property.Names.PATTERNNAME));
|
|
646
707
|
pattern.forEach((patternLine) => {
|
|
647
|
-
file.writeGroupCode('53', patternLine.angle + this.
|
|
708
|
+
file.writeGroupCode('53', patternLine.angle + this.getProperty(Property.Names.ANGLE)); // Pattern line angle
|
|
648
709
|
file.writeGroupCode('43', patternLine.xOrigin); // Pattern line base X
|
|
649
710
|
file.writeGroupCode('44', patternLine.yOrigin); // Pattern line base y
|
|
650
|
-
const deltaPoint = new Point(patternLine.xDelta * this.
|
|
651
|
-
const rotatedDelta = deltaPoint.rotate(new Point(), Utils.degrees2radians(patternLine.angle + this.
|
|
711
|
+
const deltaPoint = new Point(patternLine.xDelta * this.getProperty(Property.Names.SCALE), patternLine.yDelta * this.getProperty(Property.Names.SCALE));
|
|
712
|
+
const rotatedDelta = deltaPoint.rotate(new Point(), Utils.degrees2radians(patternLine.angle + this.getProperty(Property.Names.ANGLE)));
|
|
652
713
|
file.writeGroupCode('45', rotatedDelta.x); // Pattern line offset x
|
|
653
714
|
file.writeGroupCode('46', rotatedDelta.y); // Pattern line offset y
|
|
654
715
|
file.writeGroupCode('79', patternLine.dashes.length); // Number of dash length items
|
|
655
716
|
patternLine.dashes.forEach((dash) => {
|
|
656
|
-
file.writeGroupCode('49', dash * this.
|
|
717
|
+
file.writeGroupCode('49', dash * this.getProperty(Property.Names.SCALE)); // Dash length
|
|
657
718
|
});
|
|
658
719
|
});
|
|
659
720
|
}
|
|
@@ -695,8 +756,9 @@ export class Hatch extends Entity {
|
|
|
695
756
|
*/
|
|
696
757
|
isInside(P) {
|
|
697
758
|
// P = P.subtract(this.points[0]);
|
|
698
|
-
|
|
699
|
-
|
|
759
|
+
const childEntities = this.getProperty(Property.Names.CHILDENTITIES);
|
|
760
|
+
for (let i = 0; i < childEntities.length; i++) {
|
|
761
|
+
const shape = childEntities[i];
|
|
700
762
|
|
|
701
763
|
if (shape.boundingBox().isInside(P)) {
|
|
702
764
|
const polyline = [...shape.points];
|
|
@@ -726,7 +788,8 @@ export class Hatch extends Entity {
|
|
|
726
788
|
* @return {BoundingBox}
|
|
727
789
|
*/
|
|
728
790
|
boundingBox() {
|
|
729
|
-
|
|
791
|
+
const childEntities = this.getProperty(Property.Names.CHILDENTITIES);
|
|
792
|
+
if (childEntities.length === 0) {
|
|
730
793
|
return new BoundingBox();
|
|
731
794
|
}
|
|
732
795
|
|
|
@@ -735,8 +798,8 @@ export class Hatch extends Entity {
|
|
|
735
798
|
let ymin = Infinity;
|
|
736
799
|
let ymax = -Infinity;
|
|
737
800
|
|
|
738
|
-
for (let i = 0; i <
|
|
739
|
-
const shape =
|
|
801
|
+
for (let i = 0; i < childEntities.length; i++) {
|
|
802
|
+
const shape = childEntities[i];
|
|
740
803
|
|
|
741
804
|
const boundingBox = shape.boundingBox();
|
|
742
805
|
|
|
@@ -758,8 +821,9 @@ export class Hatch extends Entity {
|
|
|
758
821
|
* @return {boolean} true if touched
|
|
759
822
|
*/
|
|
760
823
|
touched(selection) {
|
|
761
|
-
|
|
762
|
-
|
|
824
|
+
const childEntities = this.getProperty(Property.Names.CHILDENTITIES);
|
|
825
|
+
for (let i = 0; i < childEntities.length; i++) {
|
|
826
|
+
if (childEntities[i].touched(selection)) {
|
|
763
827
|
return true;
|
|
764
828
|
}
|
|
765
829
|
}
|
|
@@ -773,10 +837,15 @@ export class Hatch extends Entity {
|
|
|
773
837
|
* @param {any} value
|
|
774
838
|
*/
|
|
775
839
|
setProperty(property, value) {
|
|
776
|
-
if (this.
|
|
777
|
-
if (property ===
|
|
840
|
+
if (this.properties.has(property)) {
|
|
841
|
+
if (property === Property.Names.PATTERNNAME) {
|
|
842
|
+
const upper = String(value).toUpperCase();
|
|
843
|
+
if (this.getProperty(Property.Names.PATTERNNAME) === upper) return;
|
|
844
|
+
super.setProperty(property, upper);
|
|
845
|
+
} else if (property === Property.Names.POINTS) {
|
|
778
846
|
// Special handling for hatch points to move child entities
|
|
779
847
|
// Consider the changes from the hatch points to be an offset and rotation
|
|
848
|
+
const hatchChildren = this.getProperty(Property.Names.CHILDENTITIES);
|
|
780
849
|
const ang = value[0].angle(value.at(-1));
|
|
781
850
|
const theta = ang - this.points[0].angle(this.points.at(-1));
|
|
782
851
|
|
|
@@ -784,26 +853,30 @@ export class Hatch extends Entity {
|
|
|
784
853
|
// set rotation center
|
|
785
854
|
const center = this.points[0];
|
|
786
855
|
// apply rotation to child entities points
|
|
787
|
-
|
|
856
|
+
hatchChildren.forEach((child) => {
|
|
788
857
|
const rotatedPoints = child.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).rotate(center, theta));
|
|
789
|
-
child.setProperty(
|
|
858
|
+
child.setProperty(Property.Names.POINTS, rotatedPoints);
|
|
790
859
|
});
|
|
791
860
|
|
|
792
|
-
this.
|
|
861
|
+
this.setProperty(Property.Names.ANGLE, this.getProperty(Property.Names.ANGLE) + Utils.radians2degrees(theta));
|
|
793
862
|
}
|
|
794
863
|
|
|
795
864
|
const delta = value[0].subtract(this.points[0]);
|
|
796
865
|
if (delta.x !== 0 || delta.y !== 0) {
|
|
797
|
-
|
|
798
|
-
|
|
866
|
+
// apply translation to child entities points
|
|
867
|
+
hatchChildren.forEach((child) => {
|
|
799
868
|
const offsetPoints = child.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
|
|
800
|
-
child.setProperty(
|
|
869
|
+
child.setProperty(Property.Names.POINTS, offsetPoints);
|
|
801
870
|
});
|
|
802
871
|
}
|
|
803
|
-
}
|
|
804
872
|
|
|
805
|
-
|
|
806
|
-
|
|
873
|
+
super.setProperty(property, value);
|
|
874
|
+
} else {
|
|
875
|
+
super.setProperty(property, value);
|
|
876
|
+
}
|
|
877
|
+
// invalidate the pattern cache — geometry, scale, angle and boundary
|
|
878
|
+
// shape are all baked into the cache
|
|
879
|
+
this.cachedPattern = null;
|
|
807
880
|
}
|
|
808
881
|
}
|
|
809
882
|
}
|