@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.
Files changed (173) hide show
  1. package/.eslintrc.json +4 -0
  2. package/.github/workflows/DesignCoreQA.yml +3 -0
  3. package/TODO.md +49 -6
  4. package/core/core/core.js +0 -3
  5. package/core/designCore.js +1 -1
  6. package/core/dimensions/alignedDimension.js +12 -11
  7. package/core/dimensions/angularDimension.js +38 -37
  8. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
  9. package/core/dimensions/diametricDimension.js +14 -13
  10. package/core/dimensions/dimension.js +62 -55
  11. package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
  12. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
  13. package/core/dimensions/radialDimension.js +12 -11
  14. package/core/dimensions/rotatedDimension.js +20 -19
  15. package/core/entities/arc.js +123 -156
  16. package/core/entities/arctext.js +222 -184
  17. package/core/entities/circle.js +77 -99
  18. package/core/entities/entity.js +114 -93
  19. package/core/entities/hatch.js +346 -273
  20. package/core/entities/insert.js +68 -83
  21. package/core/entities/line.js +21 -142
  22. package/core/entities/lwpolyline.js +16 -11
  23. package/core/entities/point.js +19 -19
  24. package/core/entities/polyline.js +7 -7
  25. package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
  26. package/core/entities/rectangle.js +3 -1
  27. package/core/entities/solid.js +6 -15
  28. package/core/entities/text.js +190 -201
  29. package/core/lib/auxiliary/rubberBand.js +32 -0
  30. package/core/lib/auxiliary/selectionWindow.js +63 -0
  31. package/core/lib/auxiliary/snapPoint.js +113 -0
  32. package/core/lib/auxiliary/trackingLine.js +76 -0
  33. package/core/lib/boundingBox.js +11 -11
  34. package/core/lib/canvas.js +232 -168
  35. package/core/lib/clipboard.js +9 -3
  36. package/core/lib/colour.js +16 -0
  37. package/core/lib/commandLine.js +1 -6
  38. package/core/lib/commandManager.js +18 -8
  39. package/core/lib/constants.js +28 -0
  40. package/core/lib/dxf/dxf.js +15 -14
  41. package/core/lib/dxf/dxfIterator.js +2 -2
  42. package/core/lib/entityManager.js +33 -26
  43. package/core/lib/inputManager.js +36 -12
  44. package/core/lib/intersect.js +25 -0
  45. package/core/lib/matrix.js +1 -1
  46. package/core/lib/patterns.js +9 -2
  47. package/core/lib/plotOptions.js +67 -0
  48. package/core/lib/polylineUtils.js +75 -0
  49. package/core/lib/renderers/cairoRenderer.js +371 -0
  50. package/core/lib/renderers/canvasRenderer.js +315 -0
  51. package/core/lib/renderers/pdfRenderer.js +366 -0
  52. package/core/lib/renderers/rendererBase.js +284 -0
  53. package/core/lib/renderers/svgRenderer.js +343 -0
  54. package/core/lib/scene.js +28 -6
  55. package/core/lib/selectionManager.js +20 -20
  56. package/core/lib/settings.js +4 -2
  57. package/core/lib/snapping.js +4 -191
  58. package/core/lib/stateManager.js +30 -1
  59. package/core/lib/utils.js +26 -7
  60. package/core/properties/entityProperties.js +104 -0
  61. package/core/properties/property.js +106 -2
  62. package/core/properties/propertyManager.js +60 -44
  63. package/core/tables/block.js +50 -81
  64. package/core/tables/dimStyle.js +5 -5
  65. package/core/tables/endblk.js +1 -1
  66. package/core/tables/layer.js +8 -0
  67. package/core/tables/ltypeManager.js +1 -1
  68. package/core/tables/styleManagerBase.js +3 -2
  69. package/core/tables/tableManagerBase.js +8 -3
  70. package/core/tables/vport.js +2 -2
  71. package/core/tools/chamfer.js +117 -143
  72. package/core/tools/chamferFilletBase.js +138 -37
  73. package/core/tools/copy.js +11 -11
  74. package/core/tools/copybase.js +3 -3
  75. package/core/tools/copyclip.js +3 -3
  76. package/core/tools/cornerEntity.js +2 -2
  77. package/core/tools/cutclip.js +3 -3
  78. package/core/tools/explode.js +7 -7
  79. package/core/tools/extend.js +138 -25
  80. package/core/tools/fillet.js +92 -189
  81. package/core/tools/matchProp.js +3 -12
  82. package/core/tools/mirror.js +15 -14
  83. package/core/tools/move.js +8 -8
  84. package/core/tools/offset.js +31 -33
  85. package/core/tools/pasteclip.js +1 -1
  86. package/core/tools/purge.js +10 -10
  87. package/core/tools/rotate.js +19 -16
  88. package/core/tools/scale.js +7 -7
  89. package/core/tools/trim.js +271 -29
  90. package/core/tools/zoom.js +1 -1
  91. package/cspell.json +321 -0
  92. package/jest.config.js +6 -0
  93. package/package.json +5 -3
  94. package/test/dimensions/alignedDimension.test.js +17 -10
  95. package/test/dimensions/angularDimension.test.js +13 -13
  96. package/test/dimensions/diametricDimension.test.js +15 -15
  97. package/test/dimensions/dimension.test.js +12 -12
  98. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
  99. package/test/dimensions/linearDimensionBase.test.js +62 -0
  100. package/test/dimensions/radialDimension.test.js +15 -15
  101. package/test/dimensions/rotatedDimension.test.js +18 -11
  102. package/test/entities/arc.test.js +83 -86
  103. package/test/entities/arctext.test.js +184 -57
  104. package/test/entities/circle.test.js +68 -96
  105. package/test/entities/entity.test.js +42 -8
  106. package/test/entities/hatch.test.js +569 -96
  107. package/test/entities/insert.test.js +99 -4
  108. package/test/entities/line.test.js +15 -41
  109. package/test/entities/lwpolyline.test.js +104 -0
  110. package/test/entities/point.test.js +3 -3
  111. package/test/entities/polyline.test.js +11 -0
  112. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
  113. package/test/entities/rectangle.test.js +11 -0
  114. package/test/entities/solid.test.js +1 -1
  115. package/test/entities/text.test.js +302 -77
  116. package/test/integration/exportIntegration.reference.dxf +9 -9
  117. package/test/integration/exportIntegration.test.js +5 -5
  118. package/test/integration/intersectionIntegration.test.js +4 -5
  119. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  120. package/test/integration/pdfRendererIntegration.test.js +61 -0
  121. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  122. package/test/integration/svgRendererIntegration.test.js +61 -0
  123. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  124. package/test/integration/userInputIntegration.test.js +239 -132
  125. package/test/lib/boundingBox.test.js +3 -3
  126. package/test/lib/canvas.test.js +744 -125
  127. package/test/lib/constants.test.js +27 -0
  128. package/test/lib/entityManager.test.js +4 -4
  129. package/test/lib/inputManager.test.js +81 -20
  130. package/test/lib/intersect.test.js +83 -0
  131. package/test/lib/plotOptions.test.js +179 -0
  132. package/test/lib/rubberBand.test.js +84 -0
  133. package/test/lib/scene.test.js +9 -9
  134. package/test/lib/selectionManager.test.js +3 -3
  135. package/test/lib/settings.test.js +5 -6
  136. package/test/lib/snapping.test.js +72 -130
  137. package/test/lib/stateManager.test.js +7 -7
  138. package/test/lib/utils.test.js +36 -2
  139. package/test/properties/entityProperties.test.js +132 -0
  140. package/test/properties/property.test.js +53 -0
  141. package/test/properties/propertyManager.test.js +25 -25
  142. package/test/renderers/canvasRenderer.test.js +317 -0
  143. package/test/renderers/pdfRenderer.test.js +271 -0
  144. package/test/renderers/rendererBase.test.js +213 -0
  145. package/test/renderers/svgRenderer.test.js +326 -0
  146. package/test/tables/block.test.js +92 -13
  147. package/test/tables/endblk.test.js +2 -2
  148. package/test/tables/layer.test.js +30 -0
  149. package/test/tables/layerManager.test.js +2 -2
  150. package/test/test-helpers/hatchGridRenderer.js +180 -0
  151. package/test/test-helpers/test-helpers.js +158 -6
  152. package/test/tools/chamfer.test.js +273 -51
  153. package/test/tools/chamferFilletBase.test.js +125 -36
  154. package/test/tools/copy.test.js +8 -8
  155. package/test/tools/copybase.test.js +2 -2
  156. package/test/tools/copyclip.test.js +2 -2
  157. package/test/tools/cornerEntity.test.js +8 -8
  158. package/test/tools/cutclip.test.js +2 -2
  159. package/test/tools/erase.test.js +6 -6
  160. package/test/tools/explode.test.js +5 -5
  161. package/test/tools/extend.test.js +373 -42
  162. package/test/tools/fillet.test.js +230 -49
  163. package/test/tools/matchProp.test.js +11 -11
  164. package/test/tools/mirror.test.js +39 -39
  165. package/test/tools/move.test.js +7 -7
  166. package/test/tools/offset.test.js +222 -55
  167. package/test/tools/pasteclip.test.js +5 -5
  168. package/test/tools/purge.test.js +4 -4
  169. package/test/tools/rotate.test.js +39 -9
  170. package/test/tools/scale.test.js +33 -33
  171. package/test/tools/trim.test.js +579 -53
  172. package/test/tools/zoom.test.js +12 -12
  173. package/core/lib/selectionWindow.js +0 -83
@@ -5,12 +5,11 @@ import { Input, PromptOptions } from '../lib/inputManager.js';
5
5
  import { Logging } from '../lib/logging.js';
6
6
  import { DXFFile } from '../lib/dxf/dxfFile.js';
7
7
  import { BoundingBox } from '../lib/boundingBox.js';
8
- import { Property } from '../properties/property.js';
9
- import { Utils } from '../lib/utils.js';
10
-
11
8
  import { DesignCore } from '../designCore.js';
12
- import { AddState, RemoveState } from '../lib/stateManager.js';
13
- import { SnapPoint } from '../lib/snapping.js';
9
+
10
+ import { Property } from '../properties/property.js';
11
+ import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
12
+ import { RubberBand } from '../lib/auxiliary/rubberBand.js';
14
13
 
15
14
  /**
16
15
  * Circle Entity Class
@@ -26,19 +25,20 @@ export class Circle extends Entity {
26
25
  constructor(data) {
27
26
  super(data);
28
27
 
29
- // add radius property with getter and setter
30
- // needs to be enumerable to appear in the object props
31
- Object.defineProperty(this, 'radius', {
32
- get: this.getRadius,
33
- set: this.setRadius,
34
- enumerable: true,
28
+ // radius: computed from points — stored in EntityProperties
29
+ this.properties.add(Property.Names.RADIUS, {
30
+ type: Property.Type.NUMBER,
31
+ get: (entity) => entity.points[1] ? entity.points[0].distance(entity.points[1]) : 0,
32
+ set: (entity, rad) => {
33
+ entity.points[1] = entity.points[0].project(0, rad);
34
+ },
35
+ dxfCode: 40,
35
36
  });
36
37
 
37
- if (data) {
38
- if (data.hasOwnProperty('radius') || data.hasOwnProperty('40')) {
39
- // DXF Groupcode 40 - Radius
40
- this.setRadius(Property.loadValue([data.radius, data[40]], 0));
41
- }
38
+ // Named scalar radius (internal API / named prop) — projects points[1] from centre.
39
+ // Full-points data (post-execute or post-fromDxf) skips this.
40
+ if (data?.radius !== undefined && this.points.length < 2) {
41
+ this.setRadius(data.radius);
42
42
  }
43
43
  }
44
44
 
@@ -54,6 +54,21 @@ export class Circle extends Entity {
54
54
  return command;
55
55
  }
56
56
 
57
+ /**
58
+ * Normalise DXF group codes into the canonical points-based representation.
59
+ * Called by the DXF loader before construction.
60
+ * @param {Object} data
61
+ * @return {Object}
62
+ */
63
+ static fromDxf(data) {
64
+ const centre = data.points?.[0];
65
+ if (!centre || data[40] === undefined) return data;
66
+ return {
67
+ ...data,
68
+ points: [centre, centre.project(0, data[40])],
69
+ };
70
+ }
71
+
57
72
  /**
58
73
  * Execute method
59
74
  * executes the workflow, requesting input required to create an entity
@@ -113,8 +128,8 @@ export class Circle extends Entity {
113
128
  preview() {
114
129
  if (this.points.length >= 1) {
115
130
  const mousePoint = DesignCore.Mouse.pointOnScene();
116
- const points = [this.points.at(-1), mousePoint];
117
- DesignCore.Scene.tempEntities.create(this.type, { points: points });
131
+ DesignCore.Scene.auxiliaryEntities.add(new RubberBand([this.points.at(0), mousePoint]));
132
+ DesignCore.Scene.previewEntities.create(this.type, { points: [this.points.at(0), mousePoint] });
118
133
  }
119
134
  }
120
135
 
@@ -139,9 +154,12 @@ export class Circle extends Entity {
139
154
  * @param {Object} ctx - context
140
155
  * @param {number} scale
141
156
  */
142
- draw(ctx, scale) {
143
- ctx.arc(this.points[0].x, this.points[0].y, this.radius, 0, 6.283);
144
- ctx.stroke();
157
+ /**
158
+ * Draw the circle
159
+ * @param {Object} renderer
160
+ */
161
+ draw(renderer) {
162
+ renderer.drawShape(this.toPolylinePoints());
145
163
  }
146
164
 
147
165
  /**
@@ -150,17 +168,17 @@ export class Circle extends Entity {
150
168
  */
151
169
  dxf(file) {
152
170
  file.writeGroupCode('0', 'CIRCLE');
153
- file.writeGroupCode('5', this.handle, DXFFile.Version.R2000); // Handle
171
+ file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
154
172
  file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
155
173
  file.writeGroupCode('100', 'AcDbCircle', DXFFile.Version.R2000);
156
- file.writeGroupCode('8', this.layer);
157
- file.writeGroupCode('6', this.lineType);
174
+ file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
175
+ file.writeGroupCode('6', this.getProperty(Property.Names.LINETYPE));
158
176
  this.writeDxfColour(file);
159
177
  file.writeGroupCode('10', this.points[0].x);
160
178
  file.writeGroupCode('20', this.points[0].y);
161
179
  file.writeGroupCode('30', '0.0');
162
- file.writeGroupCode('39', this.lineWidth);
163
- file.writeGroupCode('40', this.radius);
180
+ file.writeGroupCode('39', this.getProperty(Property.Names.LINEWIDTH));
181
+ file.writeGroupCode('40', this.getProperty(Property.Names.RADIUS));
164
182
  }
165
183
 
166
184
  /**
@@ -168,76 +186,34 @@ export class Circle extends Entity {
168
186
  * @return {Array}
169
187
  */
170
188
  toPolylinePoints() {
171
- const startPoint = this.points[0].project(0, this.radius);
189
+ const radius = this.getProperty(Property.Names.RADIUS);
190
+ const startPoint = this.points[0].project(0, radius);
172
191
  startPoint.bulge = 1;
173
- const endPoint = this.points[0].project(0, -this.radius);
192
+ const endPoint = this.points[0].project(0, -radius);
174
193
  endPoint.bulge = 1;
175
- const closurePoint = this.points[0].project(0, this.radius);
194
+ const closurePoint = this.points[0].project(0, radius);
176
195
  return [startPoint, endPoint, closurePoint];
177
196
  }
178
197
 
179
198
  /**
180
- * Set entity points from a polyline point representation
199
+ * Set entity points from a polyline point representation.
200
+ * If the points describe a full circle (start === end), mutates this Circle
201
+ * in place and returns it. Otherwise creates and returns an Arc, since a
202
+ * Circle cannot represent an open arc.
181
203
  * @param {Array} points
204
+ * @return {Circle|Arc}
182
205
  */
183
206
  fromPolylinePoints(points) {
184
- const center = points[0].bulgeCentrePoint(points[1]);
185
- const radius = center.distance(points[0]);
186
- this.points = [center, center.project(0, radius)];
187
- }
188
-
189
- /**
190
- * Trim the entity
191
- * @param {Array} intersections
192
- * @return {Array} - array of state changes
193
- */
194
- trim(intersections) {
195
- // array to hold state changes
196
- const stateChanges = [];
197
-
198
- if (intersections?.length === 0 || !intersections) {
199
- return stateChanges;
207
+ if (points.length >= 3 && points[0].isSame(points.at(-1))) {
208
+ const centre = points[0].bulgeCentrePoint(points[1]);
209
+ const radius = centre.distance(points[0]);
210
+ this.points = [centre, centre.project(0, radius)];
211
+ return this;
200
212
  }
201
-
202
- const direction = 1; // Circle is always CCW for trimming purposes
203
-
204
- // get the mouse position
205
- const mousePosition = DesignCore.Mouse.pointOnScene();
206
- // get the point on the arc closest to the mouse
207
- const pointOnCircle = mousePosition.closestPointOnArc(this.points[1], this.points[1], this.points[0]);
208
- // sort intersection points
209
- Utils.sortPointsOnArc(intersections, this.points[0], this.points[0], this.points[0], direction);
210
- // Repeat the first point to close the circle
211
- const testPoints = [...intersections, intersections.at(0)]; // Closing the circle
212
-
213
- // Test if mouse position is between two intersection points
214
- if (testPoints.length > 1) {
215
- for (let i = 0; i < testPoints.length - 1; i++) {
216
- const startPoint = testPoints[i];
217
- const endPoint = testPoints[i + 1];
218
-
219
- // check if the mouse is between startPoint and endPoint
220
- if (pointOnCircle.isOnArc(startPoint, endPoint, this.points[0], direction)) {
221
- // create a new arc entity
222
- const arc = DesignCore.CommandManager.createNew('Arc', this);
223
- arc.handle = undefined;
224
- // flip direction
225
- arc.direction = direction * -1;
226
- arc.points = [this.points[0], startPoint, endPoint];
227
- const addState = new AddState(arc);
228
- stateChanges.push(addState);
229
-
230
- // Remove the existing arc
231
- if (stateChanges.length > 0) {
232
- const removeState = new RemoveState(this);
233
- stateChanges.push(removeState);
234
- return stateChanges;
235
- }
236
- }
237
- }
238
- }
239
-
240
- return stateChanges;
213
+ const arc = DesignCore.CommandManager.createNew('Arc', this);
214
+ arc.setProperty('handle', undefined);
215
+ arc.fromPolylinePoints(points);
216
+ return arc;
241
217
  }
242
218
 
243
219
  /**
@@ -251,13 +227,14 @@ export class Circle extends Entity {
251
227
 
252
228
  const centre = this.points[0]; // circle centre point
253
229
 
230
+ const radius = this.getProperty(Property.Names.RADIUS);
254
231
  snaps.push(new SnapPoint(centre, SnapPoint.Type.CENTRE));
255
232
 
256
233
  snaps.push(
257
- new SnapPoint(new Point(centre.x + this.radius, centre.y), SnapPoint.Type.QUADRANT), // 0°
258
- new SnapPoint(new Point(centre.x, centre.y + this.radius), SnapPoint.Type.QUADRANT), // 90°
259
- new SnapPoint(new Point(centre.x - this.radius, centre.y), SnapPoint.Type.QUADRANT), // 180°
260
- new SnapPoint(new Point(centre.x, centre.y - this.radius), SnapPoint.Type.QUADRANT), // 270°
234
+ new SnapPoint(new Point(centre.x + radius, centre.y), SnapPoint.Type.QUADRANT), // 0°
235
+ new SnapPoint(new Point(centre.x, centre.y + radius), SnapPoint.Type.QUADRANT), // 90°
236
+ new SnapPoint(new Point(centre.x - radius, centre.y), SnapPoint.Type.QUADRANT), // 180°
237
+ new SnapPoint(new Point(centre.x, centre.y - radius), SnapPoint.Type.QUADRANT), // 270°
261
238
  );
262
239
 
263
240
  if (mousePoint) {
@@ -274,16 +251,16 @@ export class Circle extends Entity {
274
251
  const angleFromCentreToInput = centre.angle(fromPoint); // angle from centre toward fromPoint
275
252
 
276
253
  // Tangent: only possible when fromPoint is outside the circle radius
277
- if (distanceToCenter > this.radius) {
278
- const tangentHalfAngle = Math.acos(this.radius / distanceToCenter); // half-angle between the two tangent directions
254
+ if (distanceToCenter > radius) {
255
+ const tangentHalfAngle = Math.acos(radius / distanceToCenter); // half-angle between the two tangent directions
279
256
 
280
- snaps.push(new SnapPoint(centre.project(angleFromCentreToInput + tangentHalfAngle, this.radius), SnapPoint.Type.TANGENT));
281
- snaps.push(new SnapPoint(centre.project(angleFromCentreToInput - tangentHalfAngle, this.radius), SnapPoint.Type.TANGENT));
257
+ snaps.push(new SnapPoint(centre.project(angleFromCentreToInput + tangentHalfAngle, radius), SnapPoint.Type.TANGENT));
258
+ snaps.push(new SnapPoint(centre.project(angleFromCentreToInput - tangentHalfAngle, radius), SnapPoint.Type.TANGENT));
282
259
  }
283
260
 
284
261
  // Perpendicular: point on the circle that lies on the line from fromPoint through the centre
285
262
  if (distanceToCenter > 0) {
286
- snaps.push(new SnapPoint(centre.project(angleFromCentreToInput, this.radius), SnapPoint.Type.PERPENDICULAR));
263
+ snaps.push(new SnapPoint(centre.project(angleFromCentreToInput, radius), SnapPoint.Type.PERPENDICULAR));
287
264
  }
288
265
  }
289
266
 
@@ -308,10 +285,11 @@ export class Circle extends Entity {
308
285
  * @return {BoundingBox}
309
286
  */
310
287
  boundingBox() {
311
- const xmin = this.points[0].x - this.radius;
312
- const xmax = this.points[0].x + this.radius;
313
- const ymin = this.points[0].y - this.radius;
314
- const ymax = this.points[0].y + this.radius;
288
+ const radius = this.getProperty(Property.Names.RADIUS);
289
+ const xmin = this.points[0].x - radius;
290
+ const xmax = this.points[0].x + radius;
291
+ const ymin = this.points[0].y - radius;
292
+ const ymax = this.points[0].y + radius;
315
293
 
316
294
  const topLeft = new Point(xmin, ymax);
317
295
  const bottomRight = new Point(xmax, ymin);
@@ -2,8 +2,9 @@ import { Colours } from '../lib/colours.js';
2
2
  import { EntityColour } from '../lib/colour.js';
3
3
  import { Intersection } from '../lib/intersect.js';
4
4
  import { Point } from './point.js';
5
- import { Strings } from '../lib/strings.js';
5
+
6
6
  import { Property } from '../properties/property.js';
7
+ import { EntityProperties } from '../properties/entityProperties.js';
7
8
 
8
9
 
9
10
  import { DesignCore } from '../designCore.js';
@@ -23,80 +24,130 @@ export class Entity {
23
24
  writable: true,
24
25
  });
25
26
 
26
- Object.defineProperty(this, 'points', {
27
- value: [],
27
+ Object.defineProperty(this, 'entityColour', {
28
+ value: new EntityColour(),
28
29
  writable: true,
29
30
  });
30
31
 
31
- Object.defineProperty(this, 'colour', {
32
- get: this.getColour,
33
- set: this.setColour,
34
- });
32
+ // DXF Groupcode 62 (normalised to colourACI by Entity.fromDxf) - ACI colour index
33
+ // DXF Groupcode 420 (normalised to trueColour by Entity.fromDxf) - 24-bit true colour
34
+ if (data?.colourACI !== undefined) {
35
+ this.entityColour.setColourFromACI(data.colourACI);
36
+ }
35
37
 
36
- Object.defineProperty(this, 'entityColour', {
37
- value: new EntityColour(),
38
+ if (data?.trueColour !== undefined) {
39
+ const trueColour = Colours.trueColourToRGB(data.trueColour);
40
+ if (trueColour) this.entityColour.setColour(trueColour);
41
+ }
42
+
43
+ // Typed property store — non-enumerable so it doesn't appear in serialisation
44
+ Object.defineProperty(this, 'properties', {
45
+ value: new EntityProperties(),
38
46
  writable: true,
39
47
  });
40
48
 
41
- this.lineWidth = 2;
42
- this.lineType = 'ByLayer';
43
- this.layer = '0';
44
-
45
49
  // DXF Groupcode 5 - Handle
46
- Object.defineProperty(this, 'handle', {
50
+ this.properties.add(Property.Names.HANDLE, {
51
+ type: Property.Type.STRING,
47
52
  value: Property.loadValue([data?.handle, data?.[5]]),
48
- writable: true,
53
+ visible: false,
54
+ dxfCode: 5,
55
+ });
56
+ // DXF Groupcode 8 - Layer name
57
+ this.properties.add(Property.Names.LAYER, {
58
+ type: Property.Type.LIST,
59
+ value: Property.loadValue([data?.layer, data?.[8]], '0'),
60
+ dxfCode: 8,
61
+ options: () => DesignCore.LayerManager.getItems().map((l) => ({ display: l.name, value: l.name })),
62
+ });
63
+ // DXF Groupcode 6 - Line type
64
+ this.properties.add(Property.Names.LINETYPE, {
65
+ type: Property.Type.LIST,
66
+ value: Property.loadValue([data?.lineType, data?.[6]], 'ByLayer'),
67
+ dxfCode: 6,
68
+ options: () => DesignCore.LTypeManager.getItems().map((s) => ({ display: s.name, value: s.name })),
49
69
  });
50
- // DXF Groupcode 6 - lineType
51
- this.lineType = Property.loadValue([data?.lineType, data?.[6]], 'ByLayer');
52
70
  // DXF Groupcode 39 - Thickness (lineWidth)
53
- this.lineWidth = Property.loadValue([data?.lineWidth, data?.[39]], 2);
54
- // DXF Groupcode 8 - layername
55
- this.layer = Property.loadValue([data?.layer, data?.[8]], '0');
56
-
57
- if (data) {
58
- if (data.hasOwnProperty('points')) {
59
- this.points = data.points;
60
- }
61
-
62
- if (data.hasOwnProperty('colour') || data.hasOwnProperty('62')) {
63
- // DXF Groupcode 62 - Color Number (present if not BYLAYER)(optional);
64
- // zero indicates BYBLOCK
65
- // 256 indicates BYLAYER;
66
- // a negative value indicates that the layer is turned off
67
- if (data.colour) {
68
- if (Colours.isRGB(data.colour)) {
69
- this.colour = data.colour;
70
- }
71
- } else if (data.hasOwnProperty('62')) {
72
- this.entityColour.setColourFromACI(Math.abs(data[62]));
73
- }
74
- }
75
-
76
- if (data.hasOwnProperty('trueColour') || data.hasOwnProperty('420')) {
77
- // DXF Groupcode 420 - true color
78
- // A 24-bit color value that should be dealt with in terms of bytes with values
79
- // of 0 to 255. The lowest byte is the blue value, the middle byte is the
80
- // green value, and the third byte is the red value. The top byte is always
81
- // 0. The group code cannot be used by custom entities for their own data
82
- // because the group code is reserved for AcDbEntity, class-level color data
83
- // and AcDbEntity, class-level transparency data
84
-
85
- const trueColour = Colours.trueColourToRGB(data.trueColour || data[420]);
86
- if (trueColour) {
87
- this.colour = trueColour;
88
- }
89
- }
71
+ this.properties.add(Property.Names.LINEWIDTH, {
72
+ type: Property.Type.NUMBER,
73
+ value: Property.loadValue([data?.lineWidth, data?.[39]], 1),
74
+ dxfCode: 39,
75
+ });
76
+ // DXF Groupcode 62/420 - colour (computed, backed by entityColour)
77
+ this.properties.add(Property.Names.COLOUR, {
78
+ type: Property.Type.COLOUR,
79
+ get: (entity) => entity.entityColour.getColour(),
80
+ set: (entity, value) => entity.entityColour.setColour(value),
81
+ dxfCode: 62,
82
+ });
83
+
84
+ // Apply colour from data if RGB was passed directly
85
+ if (data?.colour && Colours.isRGB(data.colour)) {
86
+ this.entityColour.setColour(data.colour);
90
87
  }
88
+
89
+ // points — stored value in EntityProperties
90
+ this.properties.add(Property.Names.POINTS, {
91
+ visible: false,
92
+ value: data?.points ?? [],
93
+ });
91
94
  }
92
95
 
96
+ /**
97
+ * Get the entity's geometry points.
98
+ * @return {Array<Point>}
99
+ */
100
+ get points() {
101
+ return this.properties.get(Property.Names.POINTS, this);
102
+ }
103
+
104
+ /**
105
+ * Set the entity's geometry points.
106
+ * @param {Array<Point>} value
107
+ */
108
+ set points(value) {
109
+ this.properties.set(Property.Names.POINTS, value, this);
110
+ }
111
+
112
+ /**
113
+ * Normalise entity-level DXF group codes before construction.
114
+ * Subclasses override this for geometric transformation; they receive data
115
+ * that has already passed through this method via CommandManager.
116
+ * @param {Object} data - raw DXF group code object
117
+ * @return {Object} normalised data
118
+ *
119
+ * Handles:
120
+ * - Group code 62: ACI colour index → colourACI (absolute value)
121
+ * - Group code 420: 24-bit true colour integer → trueColour
122
+ */
123
+ static fromDxf(data) {
124
+ const normalised = { ...data };
125
+ // DXF group code 62 - Color Number (ACI)
126
+ if (data[62] !== undefined) {
127
+ normalised.colourACI = Math.abs(data[62]);
128
+ }
129
+ // DXF group code 420 - True Color (24-bit integer)
130
+ if (data[420] !== undefined) {
131
+ normalised.trueColour = data[420];
132
+ }
133
+ return normalised;
134
+ }
135
+
136
+ /**
137
+ * Get a property value by name.
138
+ * @param {string} name
139
+ * @return {any}
140
+ */
141
+ getProperty(name) {
142
+ return this.properties.get(name, this);
143
+ }
93
144
 
94
145
  /**
95
146
  * get rgb colour
96
147
  * @return {number} rgb colour object
97
148
  */
98
149
  getColour() {
99
- return this.entityColour.getColour(); ;
150
+ return this.entityColour.getColour();
100
151
  }
101
152
 
102
153
  /**
@@ -107,30 +158,22 @@ export class Entity {
107
158
  let rgb = this.getColour();
108
159
 
109
160
  if (this.entityColour.byLayer) {
110
- const layer = DesignCore.LayerManager.getItemByName(this.layer);
161
+ const layer = DesignCore.LayerManager.getItemByName(this.getProperty(Property.Names.LAYER));
111
162
  rgb = layer?.colour;
112
163
  }
113
164
 
114
165
  return rgb;
115
166
  }
116
167
 
117
- /**
118
- * Set the entity colour
119
- * @param {Object} rgb
120
- */
121
- setColour(rgb) {
122
- this.entityColour.setColour(rgb);
123
- }
124
-
125
168
  /**
126
169
  * Get the line type
127
170
  * @return {LType}
128
171
  */
129
172
  getLineType() {
130
- let lineTypeName = this.lineType;
173
+ let lineTypeName = this.getProperty(Property.Names.LINETYPE) ?? 'ByLayer';
131
174
 
132
175
  if (lineTypeName.toUpperCase() === 'BYLAYER') {
133
- const layer = DesignCore.LayerManager.getItemByName(this.layer);
176
+ const layer = DesignCore.LayerManager.getItemByName(this.getProperty(Property.Names.LAYER));
134
177
  lineTypeName = layer?.lineType;
135
178
  }
136
179
 
@@ -148,7 +191,7 @@ export class Entity {
148
191
  file.writeGroupCode('62', this.entityColour.aci);
149
192
  }
150
193
  if (this.entityColour.isTrueColour) {
151
- file.writeGroupCode('420', Colours.rgbToTrueColour(this.colour));
194
+ file.writeGroupCode('420', Colours.rgbToTrueColour(this.getColour()));
152
195
  }
153
196
  }
154
197
 
@@ -158,7 +201,7 @@ export class Entity {
158
201
  * @return {boolean} true if within
159
202
  */
160
203
  within(selection) {
161
- const layer = DesignCore.LayerManager.getItemByName(this.layer);
204
+ const layer = DesignCore.LayerManager.getItemByName(this.getProperty(Property.Names.LAYER));
162
205
 
163
206
  if (!layer?.isSelectable) {
164
207
  return;
@@ -182,7 +225,7 @@ export class Entity {
182
225
  * @return {boolean} true if touched
183
226
  */
184
227
  touched(selection) {
185
- const layer = DesignCore.LayerManager.getItemByName(this.layer);
228
+ const layer = DesignCore.LayerManager.getItemByName(this.getProperty(Property.Names.LAYER));
186
229
 
187
230
  if (!layer?.isSelectable) {
188
231
  return;
@@ -205,36 +248,14 @@ export class Entity {
205
248
  return false;
206
249
  }
207
250
 
208
- /**
209
- * Extend the entity
210
- * @param {Array} points
211
- * @return {Array} - array of state changes
212
- */
213
- extend(points) {
214
- // extend function to be overridden by implementation
215
- DesignCore.Core.notify(`${this.type} - ${Strings.Message.NOEXTEND}`);
216
- return [];
217
- }
218
-
219
- /**
220
- * Trim the entity
221
- * @param {Array} points
222
- * @return {Array} - array of state changes
223
- */
224
- trim(points) {
225
- // trim function to be overridden by implementation
226
- DesignCore.Core.notify(`${this.type} - ${Strings.Message.NOTRIM}`);
227
- return [];
228
- }
229
-
230
251
  /**
231
252
  * Set a property if it exists
232
253
  * @param {string} property
233
254
  * @param {any} value
234
255
  */
235
256
  setProperty(property, value) {
236
- if (this.hasOwnProperty(property)) {
237
- this[property] = value;
257
+ if (this.properties.has(property)) {
258
+ this.properties.set(property, value, this);
238
259
  }
239
260
  }
240
261
  }