@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,11 +5,11 @@ import { Block } from '../tables/block.js';
5
5
  import { BoundingBox } from '../lib/boundingBox.js';
6
6
  import { Point } from './point.js';
7
7
  import { Utils } from '../lib/utils.js';
8
- import { Property } from '../properties/property.js';
9
8
  import { Logging } from '../lib/logging.js';
9
+ import { Property } from '../properties/property.js';
10
10
 
11
11
  import { DesignCore } from '../designCore.js';
12
- import { SnapPoint } from '../lib/snapping.js';
12
+ import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
13
13
 
14
14
 
15
15
  /**
@@ -34,59 +34,29 @@ export class Insert extends Entity {
34
34
  writable: true,
35
35
  });
36
36
 
37
- // add rotation property with getter and setter
38
- // needs to be enumerable to appear in the object props
39
- Object.defineProperty(this, 'rotation', {
40
- get: this.getRotation,
41
- set: this.setRotation,
42
- enumerable: true,
37
+ this.properties.add(Property.Names.ROTATION, {
38
+ type: Property.Type.NUMBER,
39
+ dxfCode: 50,
40
+ get: (entity) => entity.getRotation(),
41
+ set: (entity, value) => entity.setRotation(value),
43
42
  });
44
43
 
45
- if (data) {
46
- if (data.hasOwnProperty('blockName') || data.hasOwnProperty('2')) {
47
- // DXF Groupcode 2 - Block name
44
+ this.properties.add(Property.Names.BLOCKNAME, {
45
+ type: Property.Type.LABEL,
46
+ readOnly: true,
47
+ get: (entity) => entity.block?.name ?? '',
48
+ });
48
49
 
49
- const blockName = data.blockName || data[2];
50
- const block = DesignCore.Scene.blockManager.getItemByName(blockName);
50
+ if (data) {
51
+ // DXF Groupcode 2 - Block name
52
+ if (data.blockName !== undefined) {
53
+ const block = DesignCore.Scene.blockManager.getItemByName(data.blockName);
51
54
  this.block = block;
52
55
  }
53
56
 
54
- if (data.hasOwnProperty('41')) {
55
- // DXF Groupcode 41 - X Scale Factor (optional, default = 1)
56
- const err = 'Groupcode 41 not implemented';
57
- Logging.instance.warn(`${this.type} - ${err}`);
58
- }
59
-
60
- if (data.hasOwnProperty('42')) {
61
- // DXF Groupcode 42 - Y Scale Factor (optional, default = 1)
62
- const err = 'Groupcode 42 not implemented';
63
- Logging.instance.warn(`${this.type} - ${err}`);
64
- }
65
-
66
- if (data.hasOwnProperty('43')) {
67
- // DXF Groupcode 43 - Z Scale Factor (optional, default = 1)
68
- const err = 'Groupcode 43 not implemented';
69
- Logging.instance.warn(`${this.type} - ${err}`);
70
- }
71
-
72
- if (data.hasOwnProperty('44')) {
73
- // DXF Groupcode 44 - Column Spacing (optional, default = 0)
74
- const err = 'Groupcode 44 not implemented';
75
- Logging.instance.warn(`${this.type} - ${err}`);
76
- }
77
-
78
- if (data.hasOwnProperty('45')) {
79
- // DXF Groupcode 45 - Row Spacing (optional, default = 0)
80
- const err = 'Groupcode 45 not implemented';
81
- Logging.instance.warn(`${this.type} - ${err}`);
82
- }
83
-
84
- if (data.hasOwnProperty('rotation') || data.hasOwnProperty('50')) {
85
- // DXF Groupcode 50 - Text Rotation, angle in degrees
86
- // if we get rotation data store this as a point[1] at an angle from point[0]
87
- // this allows all the entities to be rotated by rotating the points i.e. not all entities have a rotation property
88
-
89
- this.setRotation(Property.loadValue([data.rotation, data[50]], 0));
57
+ // Named scalar rotation (internal API); DXF code 50 is handled by fromDxf
58
+ if (data.rotation !== undefined) {
59
+ this.setRotation(data.rotation);
90
60
  } else {
91
61
  // create points[1] used to determine the rotation
92
62
  if (this.points[0] === undefined) {
@@ -95,18 +65,6 @@ export class Insert extends Entity {
95
65
 
96
66
  this.points[1] = this.points[0].add(new Point(10, 0));
97
67
  }
98
-
99
- if (data.hasOwnProperty('70')) {
100
- // DXF Groupcode 70 - Column Count (optional, default = 1)
101
- const err = 'Groupcode 70 not implemented';
102
- Logging.instance.warn(`${this.type} - ${err}`);
103
- }
104
-
105
- if (data.hasOwnProperty('71')) {
106
- // DXF Groupcode 71 - Row Count(optional, default = 1)
107
- const err = 'Groupcode 71 not implemented';
108
- Logging.instance.warn(`${this.type} - ${err}`);
109
- }
110
68
  }
111
69
  }
112
70
 
@@ -122,6 +80,40 @@ export class Insert extends Entity {
122
80
  return command;
123
81
  }
124
82
 
83
+ /**
84
+ * Normalise raw DXF data before construction
85
+ * @param {Object} data - raw DXF group code object
86
+ * @return {Object} normalised data with blockName and rotation encoded as points[1]
87
+ *
88
+ * Handles:
89
+ * - Group code 2: block name
90
+ * - Group code 50: rotation angle (degrees) encoded into points[1]
91
+ * - Group codes 41–45, 70, 71: unimplemented — logged as warnings
92
+ */
93
+ static fromDxf(data) {
94
+ const normalised = { ...data };
95
+
96
+ // DXF group code 2 - Block name
97
+ if (data[2] !== undefined) {
98
+ normalised.blockName = data[2];
99
+ }
100
+
101
+ // DXF group code 50 - rotation angle in degrees; encode into points
102
+ if (data[50] !== undefined) {
103
+ normalised.rotation = data[50];
104
+ }
105
+
106
+ // Unimplemented group codes
107
+ const unimplemented = { 41: 'X Scale Factor', 42: 'Y Scale Factor', 43: 'Z Scale Factor', 44: 'Column Spacing', 45: 'Row Spacing', 70: 'Column Count', 71: 'Row Count' };
108
+ for (const [code, label] of Object.entries(unimplemented)) {
109
+ if (data[code] !== undefined) {
110
+ Logging.instance.warn(`Insert - Groupcode ${code} (${label}) not implemented`);
111
+ }
112
+ }
113
+
114
+ return normalised;
115
+ }
116
+
125
117
  /**
126
118
  * Execute method
127
119
  * executes the workflow, requesting input required to create an entity
@@ -144,37 +136,30 @@ export class Insert extends Entity {
144
136
  */
145
137
  dxf(file) {
146
138
  file.writeGroupCode('0', 'INSERT');
147
- file.writeGroupCode('5', this.handle, DXFFile.Version.R2000); // Handle
139
+ file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
148
140
  file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
149
- file.writeGroupCode('8', this.layer);
141
+ file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
150
142
  this.writeDxfColour(file);
151
143
  file.writeGroupCode('100', 'AcDbBlockReference', DXFFile.Version.R2000);
152
144
  file.writeGroupCode('2', this.block.name);
153
145
  file.writeGroupCode('10', this.points[0].x);
154
146
  file.writeGroupCode('20', this.points[0].y);
155
147
  file.writeGroupCode('30', '0.0');
156
- if (this.rotation) {
157
- file.writeGroupCode('50', this.rotation);
148
+ if (this.getProperty(Property.Names.ROTATION)) {
149
+ file.writeGroupCode('50', this.getProperty(Property.Names.ROTATION));
158
150
  }
159
151
  }
160
152
 
161
153
  /**
162
154
  * Draw the entity
163
- * @param {Object} ctx - context
164
- * @param {number} scale
155
+ * @param {Object} renderer
156
+ * @return {Array} block entities for the canvas to render recursively
165
157
  */
166
- draw(ctx, scale) {
158
+ draw(renderer) {
167
159
  // blocks are associated with an insert point.
168
- // translate ctx by the insert location
169
- // this allows the block items to be draw without knowing the insert location
170
-
171
- ctx.save();
172
- ctx.translate(this.points[0].x, this.points[0].y);
173
- const rotation = Utils.degrees2radians(this.rotation);
174
- ctx.rotate(rotation);
175
- // pass *this* to the block to allow colour ByBlock
176
- this.block.draw(ctx, scale, this);
177
- ctx.restore();
160
+ // Apply the insert location and rotation so block entities draw correctly.
161
+ renderer.applyTransform({ x: this.points[0].x, y: this.points[0].y, rotation: Utils.degrees2radians(this.getRotation()) });
162
+ return this.block.entities;
178
163
  }
179
164
 
180
165
  /**
@@ -219,10 +204,10 @@ export class Insert extends Entity {
219
204
 
220
205
  for (let snap = 0; snap < blockSnaps.length; snap++) {
221
206
  const sp = blockSnaps[snap];
222
- // offset the item snap point by the block insert location
207
+ // offset the entity snap point by the block insert location
223
208
  const rotatedPoint = sp.snapPoint.add(this.points[0]);
224
- // rotate the snap point to match the item positions
225
- const adjustedPoint = rotatedPoint.rotate(this.points[0], Utils.degrees2radians(this.rotation));
209
+ // rotate the snap point to match the entity positions
210
+ const adjustedPoint = rotatedPoint.rotate(this.points[0], Utils.degrees2radians(this.getRotation()));
226
211
  snaps.push(new SnapPoint(adjustedPoint, sp.type));
227
212
  }
228
213
 
@@ -250,7 +235,7 @@ export class Insert extends Entity {
250
235
  closestPoint(P) {
251
236
  // get the closest point from the blocks entities
252
237
  // rotate P to match the block rotation
253
- const rotatedPoint = P.rotate(this.points[0], Utils.degrees2radians(-this.rotation));
238
+ const rotatedPoint = P.rotate(this.points[0], Utils.degrees2radians(-this.getRotation()));
254
239
  // adjust P by the insert position
255
240
  const adjustedPoint = rotatedPoint.subtract(this.points[0]);
256
241
  return this.block.closestPoint(adjustedPoint);
@@ -273,7 +258,7 @@ export class Insert extends Entity {
273
258
  * @return {boolean} true if touched
274
259
  */
275
260
  touched(selection) {
276
- const layer = DesignCore.LayerManager.getItemByName(this.layer);
261
+ const layer = DesignCore.LayerManager.getItemByName(this.getProperty(Property.Names.LAYER));
277
262
 
278
263
  if (!layer?.isSelectable) {
279
264
  return;
@@ -5,11 +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 { AddState, RemoveState, UpdateState } from '../lib/stateManager.js';
8
+ import { Property } from '../properties/property.js';
9
9
 
10
10
  import { DesignCore } from '../designCore.js';
11
- import { Utils } from '../lib/utils.js';
12
- import { SnapPoint } from '../lib/snapping.js';
11
+
12
+ import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
13
13
 
14
14
 
15
15
  /**
@@ -70,13 +70,18 @@ export class Line extends Entity {
70
70
  if (pt2 === undefined) break;
71
71
 
72
72
  if (Input.getType(pt2) === Input.Type.STRING && pt2 === 'Close') {
73
- this.points.push(pt1);
74
- DesignCore.Scene.inputManager.executeCommand(this);
73
+ // Commit a 2-point segment from the last accumulated point back to pt1
74
+ const segment = new Line({ points: [this.points.at(-1), pt1] });
75
+ DesignCore.Scene.inputManager.executeCommand(segment);
75
76
  return;
76
77
  }
77
78
 
79
+ // Commit a fresh 2-point segment; keep this.points as the accumulator
80
+ // so preview() always draws from the last confirmed point to the mouse.
81
+ const segStart = this.points.at(-1);
78
82
  this.points.push(pt2);
79
- DesignCore.Scene.inputManager.actionCommand(this);
83
+ const segment = new Line({ points: [segStart, pt2] });
84
+ DesignCore.Scene.inputManager.actionCommand(segment);
80
85
  }
81
86
  } catch (err) {
82
87
  Logging.instance.error(`${this.type} - ${err}`);
@@ -90,19 +95,16 @@ export class Line extends Entity {
90
95
  if (this.points.length >= 1) {
91
96
  const mousePoint = DesignCore.Mouse.pointOnScene();
92
97
  const points = [this.points.at(-1), mousePoint];
93
- DesignCore.Scene.tempEntities.create(this.type, { points: points });
98
+ DesignCore.Scene.previewEntities.create(this.type, { points: points });
94
99
  }
95
100
  }
96
101
 
97
102
  /**
98
103
  * Draw the entity
99
- * @param {Object} ctx - context
100
- * @param {number} scale
104
+ * @param {Object} renderer
101
105
  */
102
- draw(ctx, scale) {
103
- ctx.moveTo(this.points[0].x, this.points[0].y);
104
- ctx.lineTo(this.points[1].x, this.points[1].y);
105
- ctx.stroke();
106
+ draw(renderer) {
107
+ renderer.drawShape(this.toPolylinePoints());
106
108
  }
107
109
 
108
110
  /**
@@ -111,18 +113,18 @@ export class Line extends Entity {
111
113
  */
112
114
  dxf(file) {
113
115
  file.writeGroupCode('0', 'LINE');
114
- file.writeGroupCode('5', this.handle, DXFFile.Version.R2000); // Handle
116
+ file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
115
117
  file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
116
118
  file.writeGroupCode('100', 'AcDbLine', DXFFile.Version.R2000);
117
- file.writeGroupCode('8', this.layer);
118
- file.writeGroupCode('6', this.lineType);
119
+ file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
120
+ file.writeGroupCode('6', this.getProperty(Property.Names.LINETYPE));
119
121
  file.writeGroupCode('10', this.points[0].x);
120
122
  file.writeGroupCode('20', this.points[0].y);
121
123
  file.writeGroupCode('30', '0.0');
122
124
  file.writeGroupCode('11', this.points[1].x);
123
125
  file.writeGroupCode('21', this.points[1].y);
124
126
  file.writeGroupCode('31', '0.0');
125
- file.writeGroupCode('39', this.lineWidth);
127
+ file.writeGroupCode('39', this.getProperty(Property.Names.LINEWIDTH));
126
128
  }
127
129
 
128
130
  /**
@@ -136,134 +138,11 @@ export class Line extends Entity {
136
138
  /**
137
139
  * Set entity points from a polyline point representation
138
140
  * @param {Array} points
141
+ * @return {Line}
139
142
  */
140
143
  fromPolylinePoints(points) {
141
144
  this.points = [new Point(points[0].x, points[0].y), new Point(points[1].x, points[1].y)];
142
- }
143
-
144
- /**
145
- * Trim the entity
146
- * @param {Array} intersections
147
- * @return {Array} - array of state changes
148
- */
149
- trim(intersections) {
150
- // array to hold state changes
151
- const stateChanges = [];
152
-
153
- if (intersections?.length === 0 || !intersections) {
154
- return stateChanges;
155
- }
156
-
157
- // get the mouse position
158
- const mousePosition = DesignCore.Mouse.pointOnScene();
159
- // get the point on the line closest to the mouse
160
- const pointOnLine = mousePosition.closestPointOnLine(this.points[0], this.points[1]);
161
- // remove any intersections that are at the end points of the line
162
- intersections = intersections.filter((p) => !p.isSame(this.points[0]) && !p.isSame(this.points[1]));
163
-
164
- Utils.sortPointsByDistance(intersections, this.points[0]);
165
-
166
- // Add line points to the start and the end of the point array
167
- const testPoints = [this.points[0], ...intersections, this.points[1]];
168
-
169
- // Test if mouse position is between two intersection points
170
- if (testPoints.length > 1) {
171
- for (let i = 0; i < testPoints.length - 1; i++) {
172
- const startPoint = testPoints[i];
173
- const endPoint = testPoints[i + 1];
174
-
175
- // check if the mouse is between startPoint and endPoint
176
- if (pointOnLine.isOnLine(startPoint, endPoint)) {
177
- const newPoints = [];
178
-
179
- for (const p of testPoints) {
180
- const inOriginalLine = this.points.indexOf(p) !== -1;
181
- const inIntersections = intersections.indexOf(p) !== -1;
182
-
183
- // Keep points which:
184
- // - The mouse is between are intersections
185
- // - The mouse is not between and in this line
186
- const isBetween = p.isSame(startPoint) || p.isSame(endPoint);
187
- if ((isBetween && inIntersections) || (!isBetween && inOriginalLine)) {
188
- newPoints.push(p);
189
- }
190
- }
191
-
192
- if (newPoints.length % 2 === 0) {
193
- // Add lines for each point pair
194
- for (let j = 0; j < newPoints.length; j += 2) {
195
- const line = Utils.cloneObject(this);
196
- line.points = [newPoints[j], newPoints[j + 1]];
197
- const addState = new AddState(line);
198
- stateChanges.push(addState);
199
- }
200
- }
201
- // Remove the existing line
202
- const removeState = new RemoveState(this);
203
- stateChanges.push(removeState);
204
- }
205
- }
206
- }
207
-
208
- return stateChanges;
209
- }
210
-
211
- /**
212
- * Extend the entity
213
- * @param {Array} intersections
214
- * @return {Array} - array of state changes
215
- */
216
- extend(intersections) {
217
- // array to hold state changes
218
- const stateChanges = [];
219
-
220
- if (intersections?.length === 0 || !intersections) {
221
- return stateChanges;
222
- }
223
-
224
- let originPoint;
225
- // Find which end is closer to the mouse
226
- if (this.points[0].distance(DesignCore.Mouse.pointOnScene()) < this.points[1].distance(DesignCore.Mouse.pointOnScene())) {
227
- originPoint = 0;
228
- } else {
229
- originPoint = 1;
230
- }
231
-
232
- Utils.sortPointsByDistance(intersections, this.points[originPoint]);
233
-
234
- const endPoint = this.points[originPoint];
235
- const adjacentPoint = this.points[1 - originPoint];
236
-
237
- // Find the closest intersection that lies beyond the endpoint
238
- const direction = endPoint.subtract(adjacentPoint);
239
- const newEndPoint = intersections.find((p) => {
240
- if (p.isSame(endPoint)) return false;
241
- if (p.subtract(endPoint).dot(direction) <= 0) return false;
242
- if (adjacentPoint.distance(p) <= adjacentPoint.distance(endPoint)) return false;
243
- return true;
244
- });
245
-
246
- if (!newEndPoint) {
247
- return stateChanges;
248
- }
249
-
250
- const newPoints = [];
251
- if (originPoint === 0) {
252
- newPoints.push(newEndPoint, this.points[1]);
253
- } else {
254
- newPoints.push(this.points[0], newEndPoint);
255
- }
256
-
257
- if (newPoints[0].isSame(this.points[0]) && newPoints[1].isSame(this.points[1])) {
258
- // No change
259
- return stateChanges;
260
- }
261
-
262
- // update the line
263
- const addState = new UpdateState(this, { points: newPoints });
264
- stateChanges.push(addState);
265
-
266
- return stateChanges;
145
+ return this;
267
146
  }
268
147
 
269
148
  /**
@@ -1,12 +1,13 @@
1
1
  import { Logging } from '../lib/logging.js';
2
- import { BasePolyline } from './basePolyline.js';
3
2
  import { Polyline } from './polyline.js';
4
3
 
5
4
  /**
6
5
  * LWPolyline Entity Class
7
- * @extends BasePolyline
6
+ * LWPolyline is a DXF import-only entity. The constructor pre-processes the
7
+ * raw DXF data and returns a {@link Polyline} instance. It is never
8
+ * instantiated as a standalone entity within the application.
8
9
  */
9
- export class Lwpolyline extends BasePolyline {
10
+ export class Lwpolyline {
10
11
  static type = 'Lwpolyline';
11
12
 
12
13
  /**
@@ -14,16 +15,20 @@ export class Lwpolyline extends BasePolyline {
14
15
  * @param {Array} data
15
16
  */
16
17
  constructor(data) {
17
- super(data);
18
-
19
- if (data) {
20
- if (data.hasOwnProperty('90')) {
21
- // DXF Groupcode 90 - Number of vertices
22
- if (this.points.length !== data[90]) {
23
- Logging.instance.error(`LWPOLYLINE Vertices Count`);
24
- }
18
+ if (data?.hasOwnProperty('90')) {
19
+ // DXF Groupcode 90 - Number of vertices
20
+ if (!Array.isArray(data.points)) {
21
+ Logging.instance.error(`LWPOLYLINE: group code 90 specifies ${data[90]} vertices but points array is missing or invalid`);
22
+ } else if (data.points.length !== data[90]) {
23
+ Logging.instance.error(`LWPOLYLINE: group code 90 specifies ${data[90]} vertices but ${data.points.length} were parsed`);
25
24
  }
26
25
  }
26
+
27
+ if (data?.points?.length >= 4 && data.points[0].isSame(data.points.at(-1))) {
28
+ data.points.pop();
29
+ data[70] = (data[70] ?? 0) | 1;
30
+ }
31
+
27
32
  return new Polyline(data);
28
33
  }
29
34
 
@@ -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} centerPoint
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, centerPoint, direction = 0) {
306
- const length = this.distance(centerPoint);
307
- const radius = centerPoint.distance(startPoint);
305
+ closestPointOnArc(startPoint, endPoint, centrePoint, direction = 0) {
306
+ const length = this.distance(centrePoint);
307
+ const radius = centrePoint.distance(startPoint);
308
308
 
309
- const Cx = centerPoint.x + radius * (this.x - centerPoint.x) / length;
310
- const Cy = centerPoint.y + radius * (this.y - centerPoint.y) / length;
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, centerPoint, direction)) {
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} centerPoint
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, centerPoint, direction = 0) {
334
+ isOnArc(startPoint, endPoint, centrePoint, direction = 0) {
335
335
  // direction: ccw arc > 0, clockwise arc <= 0
336
- const snapAngle = centerPoint.angle(this);
337
- const startAngle = centerPoint.angle(startPoint);
338
- const endAngle = centerPoint.angle(endPoint);
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 center to cord midpoint
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 center point is inverted. i.e. at 180 it goes inside the arc
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 center point
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 center = this.bulgeCentrePoint(nextPoint);
489
+ const centre = this.bulgeCentrePoint(nextPoint);
490
490
  const direction = this.bulge > 0 ? 1 : -1;
491
491
 
492
- const startAngle = center.angle(this);
493
- const endAngle = center.angle(nextPoint);
494
- const splitAngle = center.angle(splitPoint);
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 { BasePolyline } from './basePolyline.js';
2
+ import { PolylineBase } from './polylineBase.js';
3
3
  import { Point } from './point.js';
4
4
 
5
5
  /**
6
6
  * Polyline Entity Class
7
- * @extends BasePolyline
7
+ * @extends PolylineBase
8
8
  */
9
- export class Polyline extends BasePolyline {
9
+ export class Polyline extends PolylineBase {
10
10
  static type = 'Polyline';
11
11
 
12
12
  /**
@@ -47,14 +47,14 @@ export class Polyline extends BasePolyline {
47
47
  if (file.version < DXFFile.Version.R2000) {
48
48
  // POLYLINE/VERTEX/SEQEND format for R12
49
49
  file.writeGroupCode('0', 'POLYLINE');
50
- file.writeGroupCode('8', this.layer);
51
- file.writeGroupCode('6', this.lineType);
50
+ file.writeGroupCode('8', this.getProperty('layer'));
51
+ file.writeGroupCode('6', this.getProperty('lineType'));
52
52
  file.writeGroupCode('66', 1); // vertices-follow flag
53
53
  file.writeGroupCode('70', this.flags.getFlagValue());
54
54
 
55
55
  for (let i = 0; i < this.points.length; i++) {
56
56
  file.writeGroupCode('0', 'VERTEX');
57
- file.writeGroupCode('8', this.layer);
57
+ file.writeGroupCode('8', this.getProperty('layer'));
58
58
  file.writeGroupCode('10', this.points[i].x);
59
59
  file.writeGroupCode('20', this.points[i].y);
60
60
  file.writeGroupCode('30', '0.0');
@@ -62,7 +62,7 @@ export class Polyline extends BasePolyline {
62
62
  }
63
63
 
64
64
  file.writeGroupCode('0', 'SEQEND');
65
- file.writeGroupCode('8', this.layer);
65
+ file.writeGroupCode('8', this.getProperty('layer'));
66
66
  return;
67
67
  }
68
68