@design-core/core 0.0.6 → 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.
Files changed (145) hide show
  1. package/.eslintrc.json +4 -0
  2. package/TODO.md +49 -6
  3. package/core/core/core.js +0 -3
  4. package/core/dimensions/alignedDimension.js +5 -4
  5. package/core/dimensions/angularDimension.js +6 -5
  6. package/core/dimensions/baseCircularDimension.js +3 -2
  7. package/core/dimensions/baseDimension.js +59 -93
  8. package/core/dimensions/baseLinearDimension.js +6 -5
  9. package/core/dimensions/diametricDimension.js +4 -3
  10. package/core/dimensions/dimension.js +21 -14
  11. package/core/dimensions/radialDimension.js +4 -3
  12. package/core/dimensions/rotatedDimension.js +12 -11
  13. package/core/entities/arc.js +112 -145
  14. package/core/entities/arctext.js +212 -174
  15. package/core/entities/basePolyline.js +30 -282
  16. package/core/entities/circle.js +77 -99
  17. package/core/entities/entity.js +114 -93
  18. package/core/entities/hatch.js +322 -249
  19. package/core/entities/insert.js +66 -81
  20. package/core/entities/line.js +21 -142
  21. package/core/entities/lwpolyline.js +16 -11
  22. package/core/entities/polyline.js +4 -4
  23. package/core/entities/rectangle.js +3 -1
  24. package/core/entities/solid.js +6 -15
  25. package/core/entities/text.js +188 -199
  26. package/core/lib/auxiliary/rubberBand.js +32 -0
  27. package/core/lib/auxiliary/selectionWindow.js +63 -0
  28. package/core/lib/auxiliary/snapPoint.js +113 -0
  29. package/core/lib/auxiliary/trackingLine.js +76 -0
  30. package/core/lib/canvas.js +228 -166
  31. package/core/lib/clipboard.js +7 -1
  32. package/core/lib/colour.js +16 -0
  33. package/core/lib/commandLine.js +1 -6
  34. package/core/lib/commandManager.js +18 -8
  35. package/core/lib/constants.js +28 -0
  36. package/core/lib/dxf/dxf.js +4 -3
  37. package/core/lib/entityManager.js +18 -11
  38. package/core/lib/inputManager.js +35 -11
  39. package/core/lib/intersect.js +25 -0
  40. package/core/lib/patterns.js +8 -1
  41. package/core/lib/plotOptions.js +67 -0
  42. package/core/lib/polylineUtils.js +75 -0
  43. package/core/lib/renderers/cairoRenderer.js +371 -0
  44. package/core/lib/renderers/canvasRenderer.js +315 -0
  45. package/core/lib/renderers/pdfRenderer.js +366 -0
  46. package/core/lib/renderers/rendererBase.js +284 -0
  47. package/core/lib/renderers/svgRenderer.js +343 -0
  48. package/core/lib/scene.js +27 -5
  49. package/core/lib/selectionManager.js +2 -2
  50. package/core/lib/settings.js +4 -2
  51. package/core/lib/snapping.js +4 -191
  52. package/core/lib/stateManager.js +30 -1
  53. package/core/lib/utils.js +21 -2
  54. package/core/properties/entityProperties.js +104 -0
  55. package/core/properties/property.js +106 -2
  56. package/core/properties/propertyManager.js +29 -13
  57. package/core/tables/block.js +4 -37
  58. package/core/tables/endblk.js +1 -1
  59. package/core/tables/layer.js +8 -0
  60. package/core/tables/styleManagerBase.js +3 -2
  61. package/core/tables/tableManagerBase.js +8 -3
  62. package/core/tools/chamfer.js +111 -137
  63. package/core/tools/chamferFilletBase.js +135 -34
  64. package/core/tools/copy.js +3 -3
  65. package/core/tools/explode.js +2 -2
  66. package/core/tools/extend.js +132 -19
  67. package/core/tools/fillet.js +86 -183
  68. package/core/tools/matchProp.js +2 -11
  69. package/core/tools/mirror.js +7 -6
  70. package/core/tools/move.js +3 -3
  71. package/core/tools/offset.js +3 -5
  72. package/core/tools/pasteclip.js +1 -1
  73. package/core/tools/rotate.js +6 -3
  74. package/core/tools/scale.js +1 -1
  75. package/core/tools/trim.js +265 -23
  76. package/core/tools/zoom.js +1 -1
  77. package/jest.config.js +6 -0
  78. package/package.json +5 -3
  79. package/test/dimensions/alignedDimension.test.js +14 -7
  80. package/test/dimensions/angularDimension.test.js +6 -6
  81. package/test/dimensions/baseDimension.test.js +69 -8
  82. package/test/dimensions/baseLinearDimension.test.js +62 -0
  83. package/test/dimensions/diametricDimension.test.js +6 -6
  84. package/test/dimensions/radialDimension.test.js +6 -6
  85. package/test/dimensions/rotatedDimension.test.js +14 -7
  86. package/test/entities/arc.test.js +80 -83
  87. package/test/entities/arctext.test.js +180 -53
  88. package/test/entities/basePolyline.test.js +68 -2
  89. package/test/entities/circle.test.js +57 -85
  90. package/test/entities/entity.test.js +42 -8
  91. package/test/entities/hatch.test.js +543 -70
  92. package/test/entities/insert.test.js +96 -1
  93. package/test/entities/line.test.js +15 -41
  94. package/test/entities/lwpolyline.test.js +104 -0
  95. package/test/entities/polyline.test.js +11 -0
  96. package/test/entities/rectangle.test.js +11 -0
  97. package/test/entities/solid.test.js +1 -1
  98. package/test/entities/text.test.js +294 -69
  99. package/test/integration/exportIntegration.reference.dxf +9 -9
  100. package/test/integration/exportIntegration.test.js +2 -2
  101. package/test/integration/intersectionIntegration.test.js +4 -5
  102. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  103. package/test/integration/pdfRendererIntegration.test.js +61 -0
  104. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  105. package/test/integration/svgRendererIntegration.test.js +61 -0
  106. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  107. package/test/integration/userInputIntegration.test.js +239 -132
  108. package/test/lib/canvas.test.js +742 -123
  109. package/test/lib/constants.test.js +27 -0
  110. package/test/lib/entityManager.test.js +4 -4
  111. package/test/lib/inputManager.test.js +80 -19
  112. package/test/lib/intersect.test.js +83 -0
  113. package/test/lib/plotOptions.test.js +179 -0
  114. package/test/lib/rubberBand.test.js +84 -0
  115. package/test/lib/settings.test.js +5 -6
  116. package/test/lib/snapping.test.js +71 -129
  117. package/test/lib/stateManager.test.js +7 -7
  118. package/test/lib/utils.test.js +36 -2
  119. package/test/properties/entityProperties.test.js +132 -0
  120. package/test/properties/property.test.js +53 -0
  121. package/test/properties/propertyManager.test.js +3 -3
  122. package/test/renderers/canvasRenderer.test.js +317 -0
  123. package/test/renderers/pdfRenderer.test.js +271 -0
  124. package/test/renderers/rendererBase.test.js +213 -0
  125. package/test/renderers/svgRenderer.test.js +326 -0
  126. package/test/tables/block.test.js +31 -1
  127. package/test/tables/endblk.test.js +2 -2
  128. package/test/tables/layer.test.js +30 -0
  129. package/test/test-helpers/hatchGridRenderer.js +180 -0
  130. package/test/test-helpers/test-helpers.js +152 -0
  131. package/test/tools/chamfer.test.js +227 -5
  132. package/test/tools/chamferFilletBase.test.js +98 -9
  133. package/test/tools/copy.test.js +2 -2
  134. package/test/tools/explode.test.js +3 -3
  135. package/test/tools/extend.test.js +331 -0
  136. package/test/tools/fillet.test.js +181 -0
  137. package/test/tools/matchProp.test.js +5 -5
  138. package/test/tools/mirror.test.js +13 -13
  139. package/test/tools/offset.test.js +170 -3
  140. package/test/tools/pasteclip.test.js +5 -5
  141. package/test/tools/rotate.test.js +32 -2
  142. package/test/tools/scale.test.js +5 -5
  143. package/test/tools/trim.test.js +526 -0
  144. package/test/tools/zoom.test.js +7 -7
  145. package/core/lib/selectionWindow.js +0 -83
@@ -8,6 +8,7 @@ import { Text } from '../../core/entities/text.js';
8
8
  import { Polyline } from '../../core/entities/polyline.js';
9
9
  import { Hatch } from '../../core/entities/hatch.js';
10
10
  import { Rectangle } from '../../core/entities/rectangle.js';
11
+ import { ArcAlignedText } from '../../core/entities/arctext.js';
11
12
  import { Copy } from '../../core/tools/copy.js';
12
13
  import { Move } from '../../core/tools/move.js';
13
14
  import { Rotate } from '../../core/tools/rotate.js';
@@ -15,9 +16,14 @@ import { Erase } from '../../core/tools/erase.js';
15
16
  import { MatchProp } from '../../core/tools/matchProp.js';
16
17
  import { Trim } from '../../core/tools/trim.js';
17
18
  import { Extend } from '../../core/tools/extend.js';
19
+ import { Mirror } from '../../core/tools/mirror.js';
20
+ import { Scale } from '../../core/tools/scale.js';
21
+ import { Offset } from '../../core/tools/offset.js';
22
+ import { Fillet } from '../../core/tools/fillet.js';
23
+ import { Chamfer } from '../../core/tools/chamfer.js';
18
24
  import { SingleSelection } from '../../core/lib/selectionManager.js';
19
25
 
20
- import { readFileSync } from 'fs';
26
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
21
27
  import { fileURLToPath } from 'url';
22
28
  import { dirname, join } from 'path';
23
29
 
@@ -26,6 +32,16 @@ const __dirname = dirname(__filename);
26
32
 
27
33
  let core;
28
34
 
35
+ // Width/height of each grid cell in DXF units.
36
+ // Entities for each command live within (col*CELL, row*CELL)→((col+1)*CELL,(row+1)*CELL).
37
+ //
38
+ // Grid layout (6 columns × 4 rows):
39
+ // Row 0: Line | Circle | Arc | Polyline | Rectangle | Text
40
+ // Row 1: Hatch | ArcText | Copy | Move | Mirror | Scale
41
+ // Row 2: Rotate | Erase | Offset | Trim | Extend | Fillet
42
+ // Row 3: Chamfer | MatchProp
43
+ const CELL = 200;
44
+
29
45
  /**
30
46
  * Create a mock for inputManager.requestInput that feeds
31
47
  * sequential inputs and rejects when exhausted (simulating Escape)
@@ -43,7 +59,8 @@ function mockRequestInput(inputs) {
43
59
  }
44
60
 
45
61
  /**
46
- * Execute an entity command with mocked user inputs
62
+ * Execute an entity creation command with mocked user inputs.
63
+ * Does NOT set activeCommand so actionCommand() routes through addItem().
47
64
  * @param {Object} command - the command instance to execute
48
65
  * @param {Array} inputs - array of input values to feed sequentially
49
66
  */
@@ -55,7 +72,8 @@ async function executeWithInputs(command, inputs) {
55
72
  }
56
73
 
57
74
  /**
58
- * Execute a tool command with mocked user inputs and pre-populated selection
75
+ * Execute a tool command with mocked user inputs and pre-populated selection.
76
+ * Sets activeCommand so actionCommand() calls tool.action().
59
77
  * @param {Object} tool - the tool instance to execute
60
78
  * @param {Array} selectionIndices - entity indices to pre-select
61
79
  * @param {Array} inputs - array of input values to feed sequentially
@@ -78,172 +96,257 @@ async function executeToolWithInputs(tool, selectionIndices, inputs) {
78
96
  beforeAll(async () => {
79
97
  core = new Core();
80
98
 
81
- // ---- Entity Creation ----
99
+ // =====================================================================
100
+ // Phase 1: Showcase entities (idx 0–7)
101
+ // =====================================================================
82
102
 
83
- // Line: two segments from (0,0) to (100,100) to (200,0)
84
- const line = new Line();
85
- await executeWithInputs(line, [
86
- new Point(0, 0),
87
- new Point(100, 100),
88
- new Point(200, 0),
103
+ // col 0, row 0 — Line → idx 0
104
+ await executeWithInputs(new Line(), [
105
+ new Point(10, 50),
106
+ new Point(90, 50),
89
107
  ]);
90
- // idx 0: Line (0,0)→(100,100)
91
- // idx 1: Line (100,100)→(200,0)
92
108
 
93
- // Circle: center (300, 300) radius 50
94
- const circle = new Circle();
95
- await executeWithInputs(circle, [
96
- new Point(300, 300),
97
- 50,
109
+ // col 1, row 0 — Circle, center (250,50) r=30 → idx 1
110
+ await executeWithInputs(new Circle(), [
111
+ new Point(250, 50),
112
+ 30,
98
113
  ]);
99
- // idx 2: Circle
100
-
101
- // Arc: center (500, 500), start point (530, 500), end angle 90 degrees
102
- const arc = new Arc();
103
- await executeWithInputs(arc, [
104
- new Point(500, 500),
105
- new Point(530, 500),
106
- 90,
114
+
115
+ // col 2, row 0 — Arc, center (450,50), start (480,50), 270° CCW → idx 2
116
+ await executeWithInputs(new Arc(), [
117
+ new Point(450, 50),
118
+ new Point(480, 50),
119
+ 270,
107
120
  ]);
108
- // idx 3: Arc
109
-
110
- // Polyline: (0, 200) -> (50, 200) -> (50, 250) -> (0, 250)
111
- const polyline = new Polyline();
112
- await executeWithInputs(polyline, [
113
- new Point(0, 200),
114
- new Point(50, 200),
115
- new Point(50, 250),
116
- new Point(0, 250),
121
+
122
+ // col 3, row 0 — Polyline, open V-shape → idx 3
123
+ await executeWithInputs(new Polyline(), [
124
+ new Point(610, 80),
125
+ new Point(650, 20),
126
+ new Point(690, 80),
117
127
  ]);
118
- // idx 4: Polyline
119
128
 
120
- // Text: position (10, 400), height 5, rotation 0, string "Hello"
121
- const text = new Text();
122
- await executeWithInputs(text, [
123
- new Point(10, 400),
124
- 5,
129
+ // col 4, row 0 — Rectangle (stored as closed Polyline) → idx 4
130
+ await executeWithInputs(new Rectangle(), [
131
+ new Point(820, 20),
132
+ new Point(880, 80),
133
+ ]);
134
+
135
+ // col 5, row 0 — Text → idx 5
136
+ await executeWithInputs(new Text(), [
137
+ new Point(1010, 50),
138
+ 10,
125
139
  0,
126
- 'Hello',
140
+ 'Text',
127
141
  ]);
128
- // idx 5: Text
129
142
 
130
- // Rectangle: corners (600, 0) and (700, 100) — creates a closed Polyline
131
- const rectangle = new Rectangle();
132
- await executeWithInputs(rectangle, [
133
- new Point(600, 0),
134
- new Point(700, 100),
143
+ // col 0, row 1 — Rectangle boundary for Hatch, in its own cell → idx 6
144
+ await executeWithInputs(new Rectangle(), [
145
+ new Point(20, 240),
146
+ new Point(180, 360),
135
147
  ]);
136
- // idx 6: Polyline (rectangle)
137
148
 
138
- // Hatch: uses the rectangle boundary as selection
139
- const rectangleEntity = DesignCore.Scene.entities.get(DesignCore.Scene.entities.count() - 1);
140
- const origSelectedItems = DesignCore.Scene.selectionManager.selectedItems;
141
- DesignCore.Scene.selectionManager.selectedItems = [rectangleEntity];
149
+ // col 0, row 1 — Hatch using the rectangle boundary (idx 6) → idx 7
150
+ {
151
+ const hatchBoundary = DesignCore.Scene.entities.get(6);
152
+ const origSelectedItems = DesignCore.Scene.selectionManager.selectedItems;
153
+ DesignCore.Scene.selectionManager.selectedItems = [hatchBoundary];
154
+ await executeWithInputs(new Hatch(), ['accept']);
155
+ DesignCore.Scene.selectionManager.selectedItems = origSelectedItems;
156
+ }
142
157
 
143
- const hatch = new Hatch();
144
- await executeWithInputs(hatch, [
145
- 'accept',
146
- ]);
147
- DesignCore.Scene.selectionManager.selectedItems = origSelectedItems;
148
- // idx 7: Hatch
149
-
150
- // Additional lines for trim and extend geometry
151
- // Trim: horizontal boundary and vertical target that cross
152
- const trimBoundary = new Line();
153
- await executeWithInputs(trimBoundary, [
154
- new Point(800, 50),
155
- new Point(1000, 50),
158
+ // col 1, row 1 — Arc for ArcAlignedText, in its own cell → idx 8
159
+ await executeWithInputs(new Arc(), [
160
+ new Point(300, 300),
161
+ new Point(350, 300),
162
+ 270,
156
163
  ]);
157
- // idx 8: Line (800,50)→(1000,50) - trim boundary
158
164
 
159
- const trimTarget = new Line();
160
- await executeWithInputs(trimTarget, [
161
- new Point(900, 0),
162
- new Point(900, 100),
165
+ // col 1, row 1 — ArcAlignedText on Arc (idx 8) → idx 9
166
+ await executeWithInputs(new ArcAlignedText(), [
167
+ new SingleSelection(8, new Point(350, 300)),
168
+ 10, // height (asked because style is STANDARD)
169
+ 'ArcText',
163
170
  ]);
164
- // idx 9: Line (900,0)→(900,100) - trim target (intersects at 900,50)
165
171
 
166
- // Extend: boundary and short target that doesn't reach it
167
- const extendBoundary = new Line();
168
- await executeWithInputs(extendBoundary, [
169
- new Point(1100, 0),
170
- new Point(1100, 200),
171
- ]);
172
- // idx 10: Line (1100,0)→(1100,200) - extend boundary
172
+ // =====================================================================
173
+ // Phase 2: Dedicated geometry for each tool cell (idx 10–25)
174
+ // =====================================================================
173
175
 
174
- const extendTarget = new Line();
175
- await executeWithInputs(extendTarget, [
176
- new Point(1000, 100),
177
- new Point(1050, 100),
178
- ]);
179
- // idx 11: Line (1000,100)→(1050,100) - extend target
176
+ // col 2, row 1 — Copy source
177
+ await executeWithInputs(new Line(), [new Point(410, 230), new Point(490, 230)]); // idx 10
178
+
179
+ // col 3, row 1 — Move source
180
+ await executeWithInputs(new Line(), [new Point(610, 230), new Point(690, 230)]); // idx 11
181
+
182
+ // col 4, row 1 — Mirror source
183
+ await executeWithInputs(new Line(), [new Point(810, 230), new Point(890, 230)]); // idx 12
184
+
185
+ // col 5, row 1 — Scale source (circle)
186
+ await executeWithInputs(new Circle(), [new Point(1050, 230), 20]); // idx 13
187
+
188
+ // col 0, row 2 — Rotate source
189
+ await executeWithInputs(new Line(), [new Point(10, 450), new Point(90, 450)]); // idx 14
190
+
191
+ // col 1, row 2 — Erase target (will be removed)
192
+ await executeWithInputs(new Line(), [new Point(210, 450), new Point(290, 450)]); // idx 15
193
+
194
+ // col 2, row 2 — Offset source
195
+ await executeWithInputs(new Line(), [new Point(410, 450), new Point(490, 450)]); // idx 16
180
196
 
181
- // MatchProp source line on a different layer
197
+ // col 3, row 2 — Trim: horizontal boundary + vertical target that crosses it
198
+ await executeWithInputs(new Line(), [new Point(620, 430), new Point(720, 430)]); // idx 17 (boundary)
199
+ await executeWithInputs(new Line(), [new Point(670, 400), new Point(670, 470)]); // idx 18 (target; intersection at 670,430)
200
+
201
+ // col 4, row 2 — Extend: vertical boundary + short horizontal target
202
+ await executeWithInputs(new Line(), [new Point(820, 410), new Point(820, 490)]); // idx 19 (boundary at x=820)
203
+ await executeWithInputs(new Line(), [new Point(840, 450), new Point(880, 450)]); // idx 20 (target; left end faces x=820)
204
+
205
+ // col 5, row 2 — Fillet: two Lines sharing a right-angle corner at (1060,440)
206
+ await executeWithInputs(new Line(), [new Point(1010, 440), new Point(1060, 440)]); // idx 21 (horizontal arm)
207
+ await executeWithInputs(new Line(), [new Point(1060, 440), new Point(1060, 490)]); // idx 22 (vertical arm)
208
+
209
+ // col 0, row 3 — Chamfer: two Lines sharing a right-angle corner at (60,620)
210
+ await executeWithInputs(new Line(), [new Point(10, 620), new Point(60, 620)]); // idx 23 (horizontal arm)
211
+ await executeWithInputs(new Line(), [new Point(60, 620), new Point(60, 670)]); // idx 24 (vertical arm)
212
+
213
+ // col 1, row 3 — MatchProp source on a dedicated layer
182
214
  DesignCore.LayerManager.addItem({ name: 'TOOL_LAYER' });
183
215
  DesignCore.LayerManager.setCstyle('TOOL_LAYER');
216
+ await executeWithInputs(new Line(), [new Point(210, 650), new Point(290, 650)]); // idx 25
217
+ DesignCore.LayerManager.setCstyle('0');
184
218
 
185
- const matchSource = new Line();
186
- await executeWithInputs(matchSource, [
187
- new Point(800, 800),
188
- new Point(900, 900),
189
- ]);
190
- // idx 12: Line on TOOL_LAYER
219
+ // =====================================================================
220
+ // Phase 3: Tool operations
221
+ // Index-shifting removals (Trim, Erase) happen last.
222
+ // =====================================================================
191
223
 
192
- DesignCore.LayerManager.setCstyle('0');
224
+ // Copy: copy idx 10 downward → adds idx 26
225
+ await executeToolWithInputs(new Copy(), [10], [
226
+ new Point(410, 230), // base point
227
+ new Point(410, 310), // destination (delta 0,+80)
228
+ ]);
193
229
 
194
- // ---- Tool Operations ----
195
- // Operations ordered so that index-shifting removals (Trim, Erase) happen last.
230
+ // Move: shift idx 11 downward (UpdateState, no new entity)
231
+ await executeToolWithInputs(new Move(), [11], [
232
+ new Point(610, 230), // base point
233
+ new Point(610, 310), // destination
234
+ ]);
196
235
 
197
- // Copy: copy line[0] offset by (0, -100)
198
- await executeToolWithInputs(new Copy(), [0], [
199
- new Point(0, 0),
200
- new Point(0, -100),
236
+ // Mirror: mirror idx 12 about a horizontal axis at y=250; keep source → adds idx 27
237
+ await executeToolWithInputs(new Mirror(), [12], [
238
+ new Point(800, 250), // first mirror-axis point
239
+ new Point(900, 250), // second mirror-axis point
240
+ 'No', // do not erase source
201
241
  ]);
202
- // idx 13: copy of line[0]
203
242
 
204
- // Move: move the copied line by (400, 0)
205
- await executeToolWithInputs(new Move(), [13], [
206
- new Point(0, 0),
207
- new Point(400, 0),
243
+ // Scale: scale idx 13 (circle) by ×2 from its centre (UpdateState)
244
+ await executeToolWithInputs(new Scale(), [13], [
245
+ new Point(1050, 230), // base point (= circle centre)
246
+ 2, // scale factor
208
247
  ]);
209
248
 
210
- // Rotate: rotate line[1] by 45 degrees around (100,100)
211
- await executeToolWithInputs(new Rotate(), [1], [
212
- new Point(100, 100),
213
- 45,
249
+ // Rotate: rotate idx 14 by 90° CCW about its left endpoint (UpdateState)
250
+ await executeToolWithInputs(new Rotate(), [14], [
251
+ new Point(10, 450), // base point
252
+ 90, // angle in degrees
214
253
  ]);
215
254
 
216
- // MatchProp: copy properties from TOOL_LAYER source (idx 12) to line[0]
217
- // Pre-select 1 item → used as source
218
- await executeToolWithInputs(new MatchProp(), [12], [
219
- { selectionSet: [0] },
255
+ // Offset: offset idx 16 upward by 20 units; pre-selecting skips SINGLESELECTION prompt → adds idx 28
256
+ await executeToolWithInputs(new Offset(), [16], [
257
+ 20, // offset distance
258
+ new Point(450, 470), // side point (above the line at y=450)
220
259
  ]);
221
- // idx 0: layer changed to TOOL_LAYER
222
260
 
223
- // Extend: extend short line (idx 11) to boundary (idx 10)
224
- // Mouse near right end → extends from right end
225
- const origPointOnScene = DesignCore.Mouse.pointOnScene.bind(DesignCore.Mouse);
226
- DesignCore.Mouse.pointOnScene = () => new Point(1050, 100);
261
+ // Extend: extend the left end of idx 20 to boundary idx 19 (at x=820)
262
+ {
263
+ const origPointOnScene = DesignCore.Mouse.pointOnScene.bind(DesignCore.Mouse);
264
+ DesignCore.Mouse.pointOnScene = () => new Point(840, 450);
265
+ await executeToolWithInputs(new Extend(), [19], [
266
+ new SingleSelection(20, new Point(840, 450)),
267
+ ]);
268
+ DesignCore.Mouse.pointOnScene = origPointOnScene;
269
+ }
227
270
 
228
- await executeToolWithInputs(new Extend(), [10], [
229
- new SingleSelection(11, new Point(1050, 100)),
271
+ // MatchProp: copy properties from idx 25 (TOOL_LAYER) onto idx 0 (UpdateState)
272
+ await executeToolWithInputs(new MatchProp(), [25], [
273
+ { selectionSet: [0] },
230
274
  ]);
231
275
 
232
- // Trim: trim vertical line (idx 9) using horizontal line (idx 8) as boundary
233
- // Mouse below intersection → removes bottom segment
234
- DesignCore.Mouse.pointOnScene = () => new Point(900, 25);
276
+ // Fillet: radius=10, trimMode=true; fillet the corner of idx 21 & idx 22 → adds Arc (idx 29)
277
+ DesignCore.Scene.headers.filletRadius = 10;
278
+ DesignCore.Scene.headers.trimMode = true;
279
+ await executeToolWithInputs(new Fillet(), [], [
280
+ new SingleSelection(21, new Point(1035, 440)), // horizontal arm, away from corner
281
+ new SingleSelection(22, new Point(1060, 465)), // vertical arm, away from corner
282
+ ]);
235
283
 
236
- await executeToolWithInputs(new Trim(), [8], [
237
- new SingleSelection(9, new Point(900, 25)),
284
+ // Chamfer: distA=distB=10, distance mode, trimMode=true → adds Line (idx 30)
285
+ DesignCore.Scene.headers.chamferDistanceA = 10;
286
+ DesignCore.Scene.headers.chamferDistanceB = 10;
287
+ DesignCore.Scene.headers.chamferMode = false;
288
+ DesignCore.Scene.headers.trimMode = true;
289
+ await executeToolWithInputs(new Chamfer(), [], [
290
+ new SingleSelection(23, new Point(35, 620)), // horizontal arm, away from corner
291
+ new SingleSelection(24, new Point(60, 645)), // vertical arm, away from corner
238
292
  ]);
239
- // idx 9 removed, trimmed line added at end
240
- // indices 10+ shift down by 1: matchprop source (was 12) → now 11
241
293
 
242
- DesignCore.Mouse.pointOnScene = origPointOnScene;
294
+ // Trim: remove the lower segment of idx 18 using boundary idx 17.
295
+ // Mouse at (670,450) — between the intersection (670,430) and the bottom end (670,470).
296
+ {
297
+ const origPointOnScene = DesignCore.Mouse.pointOnScene.bind(DesignCore.Mouse);
298
+ DesignCore.Mouse.pointOnScene = () => new Point(670, 450);
299
+ await executeToolWithInputs(new Trim(), [17], [
300
+ new SingleSelection(18, new Point(670, 450)),
301
+ ]);
302
+ DesignCore.Mouse.pointOnScene = origPointOnScene;
303
+ }
304
+ // → idx 18 removed; trimmed-remainder line added at end of entity list
305
+
306
+ // Erase: remove the dedicated erase-target (idx 15).
307
+ // idx 15 < 18, so the Trim removal above did not affect its index.
308
+ await executeToolWithInputs(new Erase(), [15], []);
309
+
310
+ // =====================================================================
311
+ // Phase 4: Text labels — placed at the top of each grid cell
312
+ // =====================================================================
313
+
314
+ const addLabel = async (col, row, name) => {
315
+ await executeWithInputs(new Text(), [
316
+ new Point(col * CELL + 10, row * CELL + 15),
317
+ 8, // height
318
+ 0, // rotation
319
+ name,
320
+ ]);
321
+ };
243
322
 
244
- // Erase: remove the matchprop source line (now idx 11 after trim shift)
245
- await executeToolWithInputs(new Erase(), [11], []);
246
- // matchprop source line removed
323
+ // Row 0
324
+ await addLabel(0, 0, 'Line');
325
+ await addLabel(1, 0, 'Circle');
326
+ await addLabel(2, 0, 'Arc');
327
+ await addLabel(3, 0, 'Polyline');
328
+ await addLabel(4, 0, 'Rectangle');
329
+ await addLabel(5, 0, 'Text');
330
+
331
+ // Row 1
332
+ await addLabel(0, 1, 'Hatch');
333
+ await addLabel(1, 1, 'ArcText');
334
+ await addLabel(2, 1, 'Copy');
335
+ await addLabel(3, 1, 'Move');
336
+ await addLabel(4, 1, 'Mirror');
337
+ await addLabel(5, 1, 'Scale');
338
+
339
+ // Row 2
340
+ await addLabel(0, 2, 'Rotate');
341
+ await addLabel(1, 2, 'Erase');
342
+ await addLabel(2, 2, 'Offset');
343
+ await addLabel(3, 2, 'Trim');
344
+ await addLabel(4, 2, 'Extend');
345
+ await addLabel(5, 2, 'Fillet');
346
+
347
+ // Row 3
348
+ await addLabel(0, 3, 'Chamfer');
349
+ await addLabel(1, 3, 'MatchProp');
247
350
  });
248
351
 
249
352
  let dxfOutput;
@@ -252,17 +355,21 @@ beforeAll(() => {
252
355
  dxfOutput = core.saveFile('R2018');
253
356
  });
254
357
 
255
- test('Test DXF output contains all entity types', () => {
358
+ test('DXF output contains all entity types', () => {
256
359
  expect(dxfOutput).toContain('LINE');
257
360
  expect(dxfOutput).toContain('CIRCLE');
258
361
  expect(dxfOutput).toContain('ARC');
259
- expect(dxfOutput).toContain('POLYLINE');
362
+ expect(dxfOutput).toContain('LWPOLYLINE');
260
363
  expect(dxfOutput).toContain('TEXT');
261
364
  expect(dxfOutput).toContain('HATCH');
365
+ expect(dxfOutput).toContain('ARCALIGNEDTEXT');
262
366
  });
263
367
 
264
- test('Test DXF output matches reference file', () => {
368
+ test('DXF output matches reference file', () => {
265
369
  const referencePath = join(__dirname, 'userInputIntegration.reference.dxf');
370
+ if (!existsSync(referencePath)) {
371
+ writeFileSync(referencePath, dxfOutput, 'utf8');
372
+ }
266
373
  const reference = readFileSync(referencePath, 'utf8');
267
374
 
268
375
  expect(dxfOutput).toBe(reference);