@design-core/core 0.0.1 → 0.0.3

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 (254) hide show
  1. package/.eslintrc.json +14 -7
  2. package/.github/workflows/DesignCoreQA.yml +3 -1
  3. package/README.md +11 -4
  4. package/TODO.md +19 -0
  5. package/core/core/core.js +49 -42
  6. package/core/designCore.js +61 -2
  7. package/core/dimensions/alignedDimension.js +142 -0
  8. package/core/dimensions/angularDimension.js +550 -0
  9. package/core/dimensions/baseCircularDimension.js +244 -0
  10. package/core/dimensions/baseDimension.js +558 -0
  11. package/core/dimensions/baseLinearDimension.js +371 -0
  12. package/core/dimensions/diametricDimension.js +82 -0
  13. package/core/dimensions/dimension.js +298 -0
  14. package/core/dimensions/linearDimension.js +29 -0
  15. package/core/dimensions/radialDimension.js +78 -0
  16. package/core/dimensions/rotatedDimension.js +167 -0
  17. package/core/entities/arc.js +314 -60
  18. package/core/entities/arctext.js +642 -0
  19. package/core/entities/basePolyline.js +553 -94
  20. package/core/entities/circle.js +181 -54
  21. package/core/entities/entity.js +154 -74
  22. package/core/entities/hatch.js +807 -0
  23. package/core/entities/insert.js +200 -39
  24. package/core/entities/line.js +196 -134
  25. package/core/entities/lwpolyline.js +21 -44
  26. package/core/entities/point.js +194 -29
  27. package/core/entities/polyline.js +54 -7
  28. package/core/entities/rectangle.js +43 -13
  29. package/core/entities/solid.js +43 -6
  30. package/core/entities/text.js +324 -181
  31. package/core/lib/boundingBox.js +73 -13
  32. package/core/lib/canvas.js +256 -65
  33. package/core/lib/clipboard.js +198 -0
  34. package/core/lib/colour.js +144 -0
  35. package/core/lib/colours.js +367 -301
  36. package/core/lib/commandLine.js +16 -28
  37. package/core/lib/commandManager.js +115 -58
  38. package/core/lib/constants.js +23 -0
  39. package/core/lib/dxf/dxf.js +249 -34
  40. package/core/lib/dxf/dxfFile.js +23 -38
  41. package/core/lib/dxf/dxfIterator.js +17 -5
  42. package/core/lib/dxf/dxfRead.js +23 -7
  43. package/core/lib/dxf/dxfWrite.js +39 -129
  44. package/core/lib/dxf/handleManager.js +97 -0
  45. package/core/lib/dxf/sections/blocks.js +16 -2
  46. package/core/lib/dxf/sections/entities.js +17 -3
  47. package/core/lib/dxf/sections/header.js +10 -1
  48. package/core/lib/dxf/sections/objects.js +36 -0
  49. package/core/lib/dxf/sections/section.js +43 -5
  50. package/core/lib/dxf/sections/tables.js +17 -3
  51. package/core/lib/entityManager.js +178 -0
  52. package/core/lib/fileio.js +14 -4
  53. package/core/lib/headers.js +229 -0
  54. package/core/lib/input.js +46 -0
  55. package/core/lib/inputManager.js +199 -194
  56. package/core/lib/intersect.js +341 -468
  57. package/core/lib/logging.js +25 -1
  58. package/core/lib/matrix.js +6 -8
  59. package/core/lib/mouse.js +24 -79
  60. package/core/lib/patterns.js +690 -0
  61. package/core/lib/promptOptions.js +133 -0
  62. package/core/lib/scene.js +71 -174
  63. package/core/lib/selectionManager.js +74 -48
  64. package/core/lib/selectionWindow.js +24 -10
  65. package/core/lib/settings.js +18 -11
  66. package/core/lib/snapping.js +129 -21
  67. package/core/lib/stateManager.js +288 -0
  68. package/core/lib/strings.js +57 -5
  69. package/core/lib/utils.js +95 -11
  70. package/core/objects/dictionary.js +43 -0
  71. package/core/objects/dictionaryManager.js +80 -0
  72. package/core/properties/dimType.js +117 -0
  73. package/core/properties/flags.js +66 -0
  74. package/core/properties/property.js +26 -0
  75. package/core/properties/propertyManager.js +65 -19
  76. package/core/tables/appId.js +31 -0
  77. package/core/tables/appIdManager.js +44 -0
  78. package/core/tables/block.js +323 -0
  79. package/core/tables/blockManager.js +48 -0
  80. package/core/tables/blockRecord.js +29 -0
  81. package/core/tables/blockRecordManager.js +37 -0
  82. package/core/tables/dimStyle.js +627 -0
  83. package/core/tables/dimStyleManager.js +187 -0
  84. package/core/tables/endblk.js +28 -0
  85. package/core/tables/layer.js +219 -0
  86. package/core/tables/layerManager.js +47 -0
  87. package/core/tables/ltype.js +85 -0
  88. package/core/tables/ltypeManager.js +65 -0
  89. package/core/tables/style.js +145 -0
  90. package/core/tables/styleManager.js +55 -0
  91. package/core/tables/styleManagerBase.js +110 -0
  92. package/core/tables/tableManagerBase.js +354 -0
  93. package/core/tables/ucs.js +28 -0
  94. package/core/tables/ucsManager.js +44 -0
  95. package/core/tables/view.js +28 -0
  96. package/core/tables/viewManager.js +44 -0
  97. package/core/tables/vport.js +70 -0
  98. package/core/tables/vportManager.js +65 -0
  99. package/core/tools/chamfer.js +343 -0
  100. package/core/tools/chamferFilletBase.js +162 -0
  101. package/core/tools/copy.js +68 -40
  102. package/core/tools/copybase.js +65 -0
  103. package/core/tools/copyclip.js +59 -0
  104. package/core/tools/cornerEntity.js +138 -0
  105. package/core/tools/cutclip.js +72 -0
  106. package/core/tools/distance.js +37 -9
  107. package/core/tools/erase.js +43 -13
  108. package/core/tools/explode.js +117 -0
  109. package/core/tools/extend.js +71 -32
  110. package/core/tools/fillet.js +336 -0
  111. package/core/tools/identify.js +33 -6
  112. package/core/tools/matchProp.js +106 -0
  113. package/core/tools/mirror.js +141 -0
  114. package/core/tools/move.js +65 -32
  115. package/core/tools/offset.js +321 -0
  116. package/core/tools/pan.js +54 -0
  117. package/core/tools/pasteclip.js +93 -0
  118. package/core/tools/purge.js +90 -0
  119. package/core/tools/rotate.js +105 -70
  120. package/core/tools/scale.js +166 -0
  121. package/core/tools/tool.js +14 -4
  122. package/core/tools/trim.js +71 -32
  123. package/core/tools/zoom.js +152 -0
  124. package/package.json +3 -305
  125. package/test/dimensions/alignedDimension.test.js +211 -0
  126. package/test/dimensions/angularDimension.test.js +243 -0
  127. package/test/dimensions/baseDimension.test.js +239 -0
  128. package/test/dimensions/diametricDimension.test.js +203 -0
  129. package/test/dimensions/dimension.test.js +102 -0
  130. package/test/dimensions/radialDimension.test.js +203 -0
  131. package/test/dimensions/rotatedDimension.test.js +224 -0
  132. package/test/entities/arc.test.js +463 -41
  133. package/test/entities/arctext.test.js +329 -0
  134. package/test/entities/basePolyline.test.js +459 -10
  135. package/test/entities/circle.test.js +312 -7
  136. package/test/entities/entity.test.js +28 -9
  137. package/test/entities/hatch.test.js +606 -0
  138. package/test/entities/insert.test.js +90 -0
  139. package/test/entities/line.test.js +182 -6
  140. package/test/entities/point.test.js +375 -37
  141. package/test/entities/solid.test.js +156 -0
  142. package/test/entities/text.test.js +533 -7
  143. package/test/integration/exportIntegration.reference.dxf +1706 -0
  144. package/test/integration/exportIntegration.test.js +186 -0
  145. package/test/integration/importExportIntegration.test.js +126 -0
  146. package/test/integration/intersectionIntegration.test.js +880 -0
  147. package/test/integration/userInputIntegration.reference.dxf +1410 -0
  148. package/test/integration/userInputIntegration.test.js +269 -0
  149. package/test/lib/boundingBox.test.js +26 -2
  150. package/test/lib/canvas.test.js +613 -5
  151. package/test/lib/clipboard.test.js +70 -0
  152. package/test/lib/colour.test.js +114 -0
  153. package/test/lib/colours.test.js +63 -18
  154. package/test/lib/commandLine.test.js +159 -22
  155. package/test/lib/commandManager.test.js +4 -4
  156. package/test/lib/constants.test.js +9 -0
  157. package/test/lib/dxf/dxfFile.test.js +6 -2
  158. package/test/lib/dxf/dxfIterator.test.js +1 -1
  159. package/test/lib/dxf/dxfWrite.test.js +173 -0
  160. package/test/lib/dxf/handleManager.test.js +137 -0
  161. package/test/lib/dxf/sections/blocks.test.js +5 -5
  162. package/test/lib/dxf/sections/entities.test.js +4 -4
  163. package/test/lib/dxf/sections/header.test.js +18 -4
  164. package/test/lib/dxf/sections/objects.test.js +132 -0
  165. package/test/lib/dxf/sections/section.test.js +29 -29
  166. package/test/lib/dxf/sections/tables.test.js +3 -3
  167. package/test/lib/entityManager.test.js +163 -0
  168. package/test/lib/headers.test.js +233 -0
  169. package/test/lib/inputManager.test.js +1144 -5
  170. package/test/lib/intersect.test.js +454 -331
  171. package/test/lib/logging.test.js +1 -1
  172. package/test/lib/matrix.test.js +1 -1
  173. package/test/lib/mouse.test.js +327 -4
  174. package/test/lib/patterns.test.js +105 -0
  175. package/test/lib/promptOptions.test.js +179 -0
  176. package/test/lib/scene.test.js +16 -52
  177. package/test/lib/selectionManager.test.js +17 -5
  178. package/test/lib/settings.test.js +111 -5
  179. package/test/lib/snapping.test.js +142 -4
  180. package/test/lib/stateManager.test.js +155 -0
  181. package/test/lib/utils.test.js +81 -1
  182. package/test/objects/dictionary.test.js +81 -0
  183. package/test/objects/dictionaryManager.test.js +34 -0
  184. package/test/properties/dimType.test.js +62 -0
  185. package/test/properties/flags.test.js +164 -0
  186. package/test/properties/property.test.js +13 -0
  187. package/test/properties/propertyManager.test.js +10 -11
  188. package/test/tables/appId.test.js +46 -0
  189. package/test/tables/appIdManager.test.js +72 -0
  190. package/test/tables/block.test.js +157 -0
  191. package/test/tables/blockManager.test.js +52 -0
  192. package/test/tables/blockRecord.test.js +39 -0
  193. package/test/tables/blockRecordManager.test.js +60 -0
  194. package/test/tables/dimstyle.test.js +72 -0
  195. package/test/tables/endblk.test.js +30 -0
  196. package/test/tables/layer.test.js +95 -0
  197. package/test/tables/layerManager.test.js +172 -0
  198. package/test/{styles → tables}/ltype.test.js +7 -7
  199. package/test/{styles → tables}/ltypeManager.test.js +2 -2
  200. package/test/tables/style.test.js +44 -0
  201. package/test/tables/stylemanagerbase.test.js +220 -0
  202. package/test/tables/tableManagerBase.test.js +194 -0
  203. package/test/tables/ucs.test.js +37 -0
  204. package/test/tables/ucsManager.test.js +48 -0
  205. package/test/tables/view.test.js +37 -0
  206. package/test/tables/viewManager.test.js +48 -0
  207. package/test/tables/vport.test.js +117 -0
  208. package/test/tables/vportManager.test.js +54 -0
  209. package/test/test-helpers/test-helpers.js +66 -0
  210. package/test/tools/chamfer.test.js +970 -0
  211. package/test/tools/chamferFilletBase.test.js +446 -0
  212. package/test/tools/copy.test.js +22 -14
  213. package/test/tools/copybase.test.js +93 -0
  214. package/test/tools/copyclip.test.js +92 -0
  215. package/test/tools/cornerEntity.test.js +201 -0
  216. package/test/tools/cutclip.test.js +94 -0
  217. package/test/tools/distance.test.js +140 -18
  218. package/test/tools/erase.test.js +12 -12
  219. package/test/tools/explode.test.js +45 -0
  220. package/test/tools/extend.test.js +326 -44
  221. package/test/tools/fillet.test.js +831 -0
  222. package/test/tools/identify.test.js +115 -16
  223. package/test/tools/matchProp.test.js +84 -0
  224. package/test/tools/mirror.test.js +475 -0
  225. package/test/tools/move.test.js +75 -19
  226. package/test/tools/offset.test.js +748 -0
  227. package/test/tools/pan.test.js +72 -0
  228. package/test/tools/pasteclip.test.js +100 -0
  229. package/test/tools/purge.test.js +89 -0
  230. package/test/tools/rotate.test.js +89 -23
  231. package/test/tools/scale.test.js +529 -0
  232. package/test/tools/trim.test.js +419 -84
  233. package/test/tools/zoom.test.js +504 -0
  234. package/core/entities/alignedDimension.js +0 -279
  235. package/core/entities/angularDimension.js +0 -194
  236. package/core/entities/baseDimension.js +0 -227
  237. package/core/entities/block.js +0 -247
  238. package/core/entities/diametricDimension.js +0 -133
  239. package/core/entities/dimension.js +0 -202
  240. package/core/entities/radialDimension.js +0 -126
  241. package/core/styles/dimStyle.js +0 -719
  242. package/core/styles/dimStyleManager.js +0 -47
  243. package/core/styles/layer.js +0 -133
  244. package/core/styles/layerManager.js +0 -40
  245. package/core/styles/ltype.js +0 -86
  246. package/core/styles/ltypeManager.js +0 -50
  247. package/core/styles/style.js +0 -158
  248. package/core/styles/styleManager.js +0 -43
  249. package/core/styles/styleManagerBase.js +0 -299
  250. package/test/entities/block.test.js +0 -22
  251. package/test/styles/dimstyle.test.js +0 -19
  252. package/test/styles/layerManager.test.js +0 -150
  253. package/test/styles/style.test.js +0 -33
  254. package/test/styles/stylemanagerbase.test.js +0 -219
@@ -0,0 +1,336 @@
1
+ import { Strings } from '../lib/strings.js';
2
+ import { ChamferFilletBase } from './chamferFilletBase.js';
3
+ import { Input, PromptOptions } from '../lib/inputManager.js';
4
+ import { Logging } from '../lib/logging.js';
5
+ import { Constants } from '../lib/constants.js';
6
+ import { Line } from '../entities/line.js';
7
+ import { BasePolyline } from '../entities/basePolyline.js';
8
+ import { AddState, RemoveState, UpdateState } from '../lib/stateManager.js';
9
+
10
+ import { DesignCore } from '../designCore.js';
11
+
12
+ /**
13
+ * Fillet Command Class
14
+ * @extends ChamferFilletBase
15
+ */
16
+ export class Fillet extends ChamferFilletBase {
17
+ /**
18
+ * Register the command
19
+ * @return {Object}
20
+ * command = name of the command
21
+ * shortcut = shortcut for the command
22
+ * type = type to group command in toolbars (omitted if not shown)
23
+ */
24
+ static register() {
25
+ const command = { command: 'Fillet', shortcut: 'F' }; // , type: 'Tool' };
26
+ return command;
27
+ }
28
+
29
+ /**
30
+ * Execute method
31
+ * executes the workflow, requesting input required to perform the command
32
+ */
33
+ async execute() {
34
+ try {
35
+ this.#notifySettings();
36
+
37
+ while (true) {
38
+ const op1 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION], ['Radius', 'Trim']);
39
+ const input1 = await DesignCore.Scene.inputManager.requestInput(op1);
40
+ if (input1 === undefined) return;
41
+
42
+ if (Input.getType(input1) === Input.Type.STRING) {
43
+ if (input1 === 'Radius') {
44
+ const rop = new PromptOptions(Strings.Input.RADIUS, [Input.Type.NUMBER]);
45
+ const r = await DesignCore.Scene.inputManager.requestInput(rop);
46
+ if (r === undefined) return;
47
+ if (r < 0) {
48
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.INVALIDNUMBER}`);
49
+ } else {
50
+ DesignCore.Scene.headers.filletRadius = r;
51
+ }
52
+ this.#notifySettings();
53
+ } else if (input1 === 'Trim') {
54
+ const top = new PromptOptions(Strings.Input.OPTION, [], ['Trim', 'No trim']);
55
+ const trimInput = await DesignCore.Scene.inputManager.requestInput(top);
56
+ if (trimInput === undefined) return;
57
+ DesignCore.Scene.headers.trimMode = (trimInput === 'Trim');
58
+ this.#notifySettings();
59
+ }
60
+ continue;
61
+ }
62
+
63
+ // input1 is a selection — validate and store first entity
64
+ const firstEntity = DesignCore.Scene.entities.get(input1.selectedItemIndex);
65
+ DesignCore.Scene.selectionManager.removeLastSelection();
66
+ if (!(firstEntity instanceof Line) && !(firstEntity instanceof BasePolyline)) {
67
+ DesignCore.Core.notify(`${this.type} - ${firstEntity.type} ${Strings.Message.NOFILLET}`);
68
+ continue;
69
+ }
70
+ if (!this.firstPick.setPick(firstEntity, input1.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOFILLET}`)) continue;
71
+
72
+ // Prompt for second object
73
+ const op2 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION]);
74
+ while (true) {
75
+ const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
76
+ if (input2 === undefined) return;
77
+
78
+ const candidate = DesignCore.Scene.entities.get(input2.selectedItemIndex);
79
+ DesignCore.Scene.selectionManager.removeLastSelection();
80
+ if (!(candidate instanceof Line) && !(candidate instanceof BasePolyline)) {
81
+ DesignCore.Core.notify(`${this.type} - ${candidate.type} ${Strings.Message.NOFILLET}`);
82
+ continue;
83
+ }
84
+ if (!this.secondPick.setPick(candidate, input2.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOFILLET}`)) continue;
85
+ // If both selections are the same polyline, segments must be consecutive
86
+ // or they must be the open-end segments of an open polyline.
87
+ if (candidate === firstEntity && firstEntity instanceof BasePolyline) {
88
+ const isConsecutive = firstEntity.areConsecutiveSegments(this.firstPick.segmentIndex, this.secondPick.segmentIndex);
89
+ const lastIdx = firstEntity.points.length - 1;
90
+ const isOpenEnds = !firstEntity.flags.hasFlag(1) &&
91
+ ((this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
92
+ (this.secondPick.segmentIndex === 1 && this.firstPick.segmentIndex === lastIdx));
93
+ if (!isConsecutive && !isOpenEnds) {
94
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Message.NONCONSECUTIVESEGMENTS}`);
95
+ continue;
96
+ }
97
+ }
98
+ break;
99
+ }
100
+
101
+ // Apply fillet then reset
102
+ DesignCore.Scene.inputManager.executeCommand();
103
+ return;
104
+ }
105
+ } catch (err) {
106
+ Logging.instance.error(`${this.type} - ${err}`);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Preview the command during execution
112
+ */
113
+ preview() {
114
+ // No preview
115
+ }
116
+
117
+ /**
118
+ * Perform the fillet
119
+ */
120
+ action() {
121
+ if (!this.firstPick.entity || !this.secondPick.entity) return;
122
+ if (!this.resolveCornerGeometry(Strings.Message.NOFILLET)) return;
123
+
124
+ // radius = 0: trim/extend both lines to the sharp intersection with no arc
125
+ const filletRadius = DesignCore.Scene.headers.filletRadius;
126
+ const trimMode = DesignCore.Scene.headers.trimMode;
127
+ if (filletRadius === 0) {
128
+ if (trimMode) {
129
+ const stateChanges = this.applySharpTrim();
130
+ DesignCore.Scene.commit(stateChanges);
131
+ }
132
+ return;
133
+ }
134
+
135
+ // Angle between the two clicked-side direction vectors (i.e. the opening angle of the chosen corner)
136
+ const firstUnit = this.firstPick.clickUnit(this.intersectionPoint);
137
+ const secondUnit = this.secondPick.clickUnit(this.intersectionPoint);
138
+ const cosAngle = Math.min(1, Math.max(-1, firstUnit.dot(secondUnit)));
139
+ const cornerAngle = Math.acos(cosAngle);
140
+
141
+ // Collinear or antiparallel lines cannot be filleted
142
+ if (cornerAngle < Constants.Tolerance.EPSILON || cornerAngle > Math.PI - Constants.Tolerance.EPSILON) {
143
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
144
+ return;
145
+ }
146
+
147
+ // Bisector unit vector: points into the chosen corner toward the fillet centre
148
+ const bisectorUnit = firstUnit.add(secondUnit).normalise();
149
+
150
+ // Distance from the intersection point to the fillet centre along the bisector
151
+ const intersectToCentreDistance = filletRadius / Math.sin(cornerAngle / 2);
152
+
153
+ // Fillet arc centre point
154
+ const arcCentre = this.intersectionPoint.add(bisectorUnit.scale(intersectToCentreDistance));
155
+
156
+ // Tangent points where the fillet arc meets each line (foot of perpendicular from centre to line)
157
+ const firstTangentPoint = arcCentre.perpendicular(this.firstPick.lineStart, this.firstPick.lineEnd);
158
+ const secondTangentPoint = arcCentre.perpendicular(this.secondPick.lineStart, this.secondPick.lineEnd);
159
+
160
+ // Verify the tangent points are reachable — same direction as the kept end from the
161
+ // intersection and not farther than the kept end (which would flip the line direction).
162
+ if (trimMode) {
163
+ const firstKeptDir = this.firstPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
164
+ const secondKeptDir = this.secondPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
165
+ const firstTangentDot = firstTangentPoint.subtract(this.intersectionPoint).dot(firstKeptDir);
166
+ const secondTangentDot = secondTangentPoint.subtract(this.intersectionPoint).dot(secondKeptDir);
167
+ if (firstTangentDot < 0 || firstTangentDot > firstKeptDir.dot(firstKeptDir) ||
168
+ secondTangentDot < 0 || secondTangentDot > secondKeptDir.dot(secondKeptDir)) {
169
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.RADIUSTOOLARGE}`);
170
+ return;
171
+ }
172
+ }
173
+
174
+ // Determine arc winding direction: if the cross product of the two centre-to-tangent
175
+ // vectors is positive, the short arc from firstTangentPoint to secondTangentPoint
176
+ // travels CCW (direction = 1), otherwise CW (direction = -1).
177
+ const centreToFirst = firstTangentPoint.subtract(arcCentre);
178
+ const centreToSecond = secondTangentPoint.subtract(arcCentre);
179
+ const windingCross = centreToFirst.cross(centreToSecond);
180
+ const arcDirection = windingCross > 0 ? 1 : -1;
181
+
182
+ const arc = DesignCore.CommandManager.createNew('Arc', {
183
+ points: [arcCentre, firstTangentPoint, secondTangentPoint],
184
+ direction: arcDirection,
185
+ });
186
+
187
+ const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
188
+ const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
189
+
190
+ if (!trimMode) {
191
+ DesignCore.Scene.commit([new AddState(arc)]);
192
+ return;
193
+ }
194
+
195
+ if (!firstIsPolyline && !secondIsPolyline) {
196
+ const stateChanges = this.#trimLineAndLine(firstTangentPoint, secondTangentPoint, arc);
197
+ DesignCore.Scene.commit(stateChanges);
198
+ } else if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
199
+ const stateChanges = this.#trimPolyAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre, arc);
200
+ DesignCore.Scene.commit(stateChanges);
201
+ } else {
202
+ const stateChanges = this.#trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre);
203
+ DesignCore.Scene.commit(stateChanges);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Apply trim and arc for a Line + Line fillet.
209
+ * @param {Point} firstTangentPoint
210
+ * @param {Point} secondTangentPoint
211
+ * @param {Arc} arc
212
+ * @return {Array}
213
+ */
214
+ #trimLineAndLine(firstTangentPoint, secondTangentPoint, arc) {
215
+ return [
216
+ new AddState(arc),
217
+ new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstTangentPoint] }),
218
+ new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondTangentPoint] }),
219
+ ];
220
+ }
221
+
222
+ /**
223
+ * Apply trim and arc for a Polyline + Polyline (same entity) fillet.
224
+ * Handles both the open-ends case and the consecutive-segments case.
225
+ * @param {Point} firstTangentPoint
226
+ * @param {Point} secondTangentPoint
227
+ * @param {number} arcDirection
228
+ * @param {Point} arcCentre
229
+ * @param {Arc} arc
230
+ * @return {Array}
231
+ */
232
+ #trimPolyAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre, arc) {
233
+ const closeSegIdx = this.firstPick.entity.points.length;
234
+ const lastIdx = closeSegIdx - 1;
235
+ const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
236
+ const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
237
+ (this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
238
+ (this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
239
+ );
240
+ const newPoints = this.firstPick.entity.points.map((p) => p.clone());
241
+ const stateChanges = [];
242
+
243
+ if (isOpenEnds) {
244
+ stateChanges.push(new AddState(arc));
245
+ if (this.firstPick.segmentIndex === 1) {
246
+ newPoints[0] = firstTangentPoint.clone();
247
+ newPoints[lastIdx] = secondTangentPoint.clone();
248
+ } else {
249
+ newPoints[0] = secondTangentPoint.clone();
250
+ newPoints[lastIdx] = firstTangentPoint.clone();
251
+ }
252
+ } else {
253
+ const seg1 = this.firstPick.segmentIndex;
254
+ const seg2 = this.secondPick.segmentIndex;
255
+ const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
256
+ const isFirstLower = seg1 < seg2;
257
+ const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
258
+ const lowerTangent = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? secondTangentPoint : firstTangentPoint;
259
+ const upperTangent = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? firstTangentPoint : secondTangentPoint;
260
+ const polyArcDir = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? -arcDirection : arcDirection;
261
+ const startAngle = arcCentre.angle(lowerTangent);
262
+ const endAngle = arcCentre.angle(upperTangent);
263
+ const includedAngle = ((endAngle - startAngle) * polyArcDir + 4 * Math.PI) % (2 * Math.PI);
264
+ const bulge = polyArcDir * Math.tan(includedAngle / 4);
265
+ const arcStartPoint = lowerTangent.clone();
266
+ arcStartPoint.bulge = bulge;
267
+ newPoints.splice(cornerIdx, 1, arcStartPoint, upperTangent.clone());
268
+ }
269
+
270
+ stateChanges.push(new UpdateState(this.firstPick.entity, { points: newPoints }));
271
+ return stateChanges;
272
+ }
273
+
274
+ /**
275
+ * Apply trim and arc for a Line + Polyline (or Polyline + Line) fillet.
276
+ * The line is consumed into the polyline with the arc encoded as a bulge.
277
+ * @param {Point} firstTangentPoint
278
+ * @param {Point} secondTangentPoint
279
+ * @param {number} arcDirection
280
+ * @param {Point} arcCentre
281
+ * @return {Array}
282
+ */
283
+ #trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre) {
284
+ const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
285
+ const [poly, line] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
286
+ const lineTangentPoint = line === this.firstPick ? firstTangentPoint : secondTangentPoint;
287
+ const polyTangentPoint = poly === this.firstPick ? firstTangentPoint : secondTangentPoint;
288
+ const polyToLineDir = firstIsPolyline ? arcDirection : -arcDirection;
289
+
290
+ const startAngle = arcCentre.angle(polyTangentPoint);
291
+ const endAngle = arcCentre.angle(lineTangentPoint);
292
+ const includedAngle = ((endAngle - startAngle) * polyToLineDir + 4 * Math.PI) % (2 * Math.PI);
293
+ const bulge = polyToLineDir * Math.tan(includedAngle / 4);
294
+ const arcStartPoint = polyTangentPoint.clone();
295
+ arcStartPoint.bulge = bulge;
296
+
297
+ const polySegIdx = poly.segmentIndex;
298
+ const keepStart = poly.keepStart(this.intersectionPoint);
299
+ let newPoints;
300
+ if (keepStart) {
301
+ // Keep the polyline points up to (not including) the selected segment start,
302
+ // then the arc start (with bulge), the line tangent point, and the kept end of the line.
303
+ newPoints = [
304
+ ...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
305
+ arcStartPoint,
306
+ lineTangentPoint.clone(),
307
+ line.lineKeptEnd(this.intersectionPoint).clone(),
308
+ ];
309
+ } else {
310
+ // Keep the polyline points from the selected segment start onwards,
311
+ // prepending the kept end of the line, the reversed arc start (with negated bulge), and the poly tangent point.
312
+ const revArcStartPoint = lineTangentPoint.clone();
313
+ revArcStartPoint.bulge = -bulge;
314
+ newPoints = [
315
+ line.lineKeptEnd(this.intersectionPoint).clone(),
316
+ revArcStartPoint,
317
+ polyTangentPoint.clone(),
318
+ ...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
319
+ ];
320
+ }
321
+
322
+ return [
323
+ new RemoveState(line.entity),
324
+ new UpdateState(poly.entity, { points: newPoints }),
325
+ ];
326
+ }
327
+
328
+ /**
329
+ * Display the current fillet settings to the command line
330
+ */
331
+ #notifySettings() {
332
+ const filletRadius = DesignCore.Scene.headers.filletRadius;
333
+ const trimMode = DesignCore.Scene.headers.trimMode;
334
+ DesignCore.Core.notify(`Current settings: Mode = ${trimMode ? 'TRIM' : 'NOTRIM'}, Radius = ${filletRadius}`);
335
+ }
336
+ }
@@ -1,24 +1,41 @@
1
- import {Strings} from '../lib/strings.js';
2
- import {Tool} from './tool.js';
3
- import {Input, PromptOptions} from '../lib/inputManager.js';
4
- import {Logging} from '../lib/logging.js';
1
+ import { Strings } from '../lib/strings.js';
2
+ import { Tool } from './tool.js';
3
+ import { Input, PromptOptions } from '../lib/inputManager.js';
4
+ import { Logging } from '../lib/logging.js';
5
5
 
6
- import {DesignCore} from '../designCore.js';
6
+ import { DesignCore } from '../designCore.js';
7
7
 
8
+ /**
9
+ * Identity Command Class
10
+ * @extends Tool
11
+ */
8
12
  export class Identify extends Tool {
13
+ /** Create an Identify command */
9
14
  constructor() {
10
15
  super();
11
16
  }
12
17
 
18
+ /**
19
+ * Register the command
20
+ * @return {Object}
21
+ * command = name of the command
22
+ * shortcut = shortcut for the command
23
+ * type = type to group command in toolbars (omitted if not shown)
24
+ */
13
25
  static register() {
14
- const command = {command: 'Identify', shortcut: 'ID'};
26
+ const command = { command: 'Identify', shortcut: 'ID' };
15
27
  return command;
16
28
  }
17
29
 
30
+ /**
31
+ * Execute method
32
+ * executes the workflow, requesting input required to perform the command
33
+ */
18
34
  async execute() {
19
35
  try {
20
36
  const op = new PromptOptions(Strings.Input.POINT, [Input.Type.POINT]);
21
37
  const pt1 = await DesignCore.Scene.inputManager.requestInput(op);
38
+ if (pt1 === undefined) return;
22
39
  this.points.push(pt1);
23
40
 
24
41
  DesignCore.Scene.inputManager.executeCommand();
@@ -27,6 +44,16 @@ export class Identify extends Tool {
27
44
  }
28
45
  }
29
46
 
47
+ /**
48
+ * Preview the command during execution
49
+ */
50
+ preview() {
51
+ // No Preview
52
+ }
53
+
54
+ /**
55
+ * Perform the command
56
+ */
30
57
  action() {
31
58
  const x = this.points.at(-1).x.toFixed(1);
32
59
  const y = this.points.at(-1).y.toFixed(1);
@@ -0,0 +1,106 @@
1
+ import { Tool } from './tool.js';
2
+ import { Input, PromptOptions } from '../lib/inputManager.js';
3
+ import { Strings } from '../lib/strings.js';
4
+ import { Logging } from '../lib/logging.js';
5
+ import { UpdateState } from '../lib/stateManager.js';
6
+
7
+ import { DesignCore } from '../designCore.js';
8
+
9
+ /**
10
+ * MatchProp Command Class
11
+ * Provides: All, Extents, Window
12
+ * @extends Tool
13
+ */
14
+ export class MatchProp extends Tool {
15
+ /** Create a MatchProp command */
16
+ constructor() {
17
+ super();
18
+ // index of source item
19
+ this.sourceIndex = null;
20
+ // array of destination item indices
21
+ this.destinationSetIndices = [];
22
+ // properties to match
23
+ this.properties = ['colour', 'layer', 'lineType', 'lineWidth', 'height', 'styleName', 'dimensionStyle'];
24
+ }
25
+
26
+ /**
27
+ * Register the command
28
+ * @return {Object}
29
+ */
30
+ static register() {
31
+ const command = { command: 'MatchProp', shortcut: 'MA' };
32
+ return command;
33
+ }
34
+
35
+ /**
36
+ * Execute method
37
+ */
38
+ async execute() {
39
+ try {
40
+ if (DesignCore.Scene.selectionManager.selectionSet.selectionSet.length === 1) {
41
+ // use selected item as source if only one selected
42
+ const selections = DesignCore.Scene.selectionManager.selectionSet.selectionSet.slice();
43
+ this.sourceIndex = selections[0];
44
+ } else {
45
+ // prompt for source item
46
+ DesignCore.Scene.selectionManager.reset();
47
+ const sourceSelection = new PromptOptions(Strings.Input.SOURCE, [Input.Type.SINGLESELECTION], []);
48
+ const input = await DesignCore.Scene.inputManager.requestInput(sourceSelection);
49
+ if (input === undefined) return;
50
+ this.sourceIndex = input.selectedItemIndex;
51
+ }
52
+
53
+ const destinationSelection = new PromptOptions(Strings.Input.DESTINATIONSET, [Input.Type.SELECTIONSET]);
54
+ const destInput = await DesignCore.Scene.inputManager.requestInput(destinationSelection);
55
+ if (destInput === undefined) return;
56
+ this.destinationSetIndices = destInput.selectionSet;
57
+
58
+ DesignCore.Scene.inputManager.executeCommand();
59
+ } catch (err) {
60
+ Logging.instance.error(`${this.type} - ${err}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Perform the command
66
+ */
67
+ action() {
68
+ // MatchProp
69
+
70
+ // return if no source or destination set
71
+ if (this.sourceIndex === null || this.destinationSetIndices.length === 0) {
72
+ return;
73
+ }
74
+
75
+ // get source item
76
+ const sourceItem = DesignCore.Scene.entities.get(this.sourceIndex);
77
+
78
+ const stateChanges = [];
79
+
80
+ // loop through destination set
81
+ for (let i = 0; i < this.destinationSetIndices.length; i++) {
82
+ const targetItem = DesignCore.Scene.entities.get(this.destinationSetIndices[i]);
83
+ const propertySet = {};
84
+ // loop through properties and match
85
+ for (let p = 0; p < this.properties.length; p++) {
86
+ // check property exists on both items
87
+ const prop = this.properties[p];
88
+ if (sourceItem.hasOwnProperty(prop)) {
89
+ propertySet[prop] = sourceItem[prop];
90
+ }
91
+ }
92
+
93
+ // only add state change if there are properties to change
94
+ if (Object.keys(propertySet).length > 0) {
95
+ const stateChange = new UpdateState(targetItem, propertySet);
96
+ stateChanges.push(stateChange);
97
+ }
98
+ }
99
+ // apply the updates
100
+ if (stateChanges.length > 0) {
101
+ DesignCore.Scene.commit(stateChanges);
102
+ }
103
+ }
104
+ }
105
+
106
+
@@ -0,0 +1,141 @@
1
+ import { Utils } from '../lib/utils.js';
2
+ import { Strings } from '../lib/strings.js';
3
+ import { Tool } from './tool.js';
4
+ import { Input, PromptOptions } from '../lib/inputManager.js';
5
+ import { Logging } from '../lib/logging.js';
6
+ import { Point } from '../entities/point.js';
7
+ import { AddState, UpdateState } from '../lib/stateManager.js';
8
+
9
+ import { DesignCore } from '../designCore.js';
10
+
11
+ /**
12
+ * Mirror Command Class
13
+ * @extends Tool
14
+ */
15
+ export class Mirror extends Tool {
16
+ /** Create a Mirror command */
17
+ constructor() {
18
+ super();
19
+ this.eraseSource = false;
20
+ }
21
+
22
+ /**
23
+ * Register the command
24
+ * @return {Object}
25
+ * command = name of the command
26
+ * shortcut = shortcut for the command
27
+ * type = type to group command in toolbars (omitted if not shown)
28
+ */
29
+ static register() {
30
+ const command = { command: 'Mirror', shortcut: 'MI' }; // , type: 'Tool' };
31
+ return command;
32
+ }
33
+
34
+ /**
35
+ * Execute method
36
+ * executes the workflow, requesting input required to perform the command
37
+ */
38
+ async execute() {
39
+ try {
40
+ const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
41
+
42
+ if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
43
+ await DesignCore.Scene.inputManager.requestInput(op);
44
+ }
45
+
46
+ const op2 = new PromptOptions(Strings.Input.MIRRORFIRST, [Input.Type.POINT]);
47
+ const pt1 = await DesignCore.Scene.inputManager.requestInput(op2);
48
+ if (pt1 === undefined) return;
49
+ this.points.push(pt1);
50
+
51
+ const op3 = new PromptOptions(Strings.Input.MIRRORSECOND, [Input.Type.POINT]);
52
+ const pt2 = await DesignCore.Scene.inputManager.requestInput(op3);
53
+ if (pt2 === undefined) return;
54
+ this.points.push(pt2);
55
+
56
+ const op4 = new PromptOptions(Strings.Input.ERASESOURCE, [], ['Yes', 'No'], 'N');
57
+ const eraseInput = await DesignCore.Scene.inputManager.requestInput(op4);
58
+ this.eraseSource = (eraseInput === 'Yes');
59
+
60
+ DesignCore.Scene.inputManager.executeCommand();
61
+ } catch (err) {
62
+ Logging.instance.error(`${this.type} - ${err}`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Preview the command during execution
68
+ */
69
+ preview() {
70
+ if (this.points.length >= 1) {
71
+ const mousePoint = DesignCore.Mouse.pointOnScene();
72
+ const pt1 = this.points[0];
73
+ const pt2 = this.points.length >= 2 ? this.points[1] : mousePoint;
74
+
75
+ // Draw the mirror line
76
+ DesignCore.Scene.tempEntities.create('Line', { points: [pt1, pt2] });
77
+
78
+ // Preview mirrored entities (skip if mirror line has zero length)
79
+ if (!pt1.isSame(pt2)) {
80
+ for (let i = 0; i < DesignCore.Scene.selectionManager.selectedItems.length; i++) {
81
+ const item = DesignCore.Scene.selectionManager.selectedItems[i];
82
+ const entityIndex = DesignCore.Scene.selectionManager.selectionSet.selectionSet[i];
83
+ const originalItem = DesignCore.Scene.entities.get(entityIndex);
84
+ item.setProperty('points', this.getMirroredPoints(originalItem.points, pt1, pt2));
85
+ if (originalItem.direction !== undefined) {
86
+ item.setProperty('direction', -originalItem.direction);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Perform the command
95
+ */
96
+ action() {
97
+ const pt1 = this.points[0];
98
+ const pt2 = this.points[1];
99
+
100
+ if (pt1.isSame(pt2)) {
101
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ZEROLENGTH}`);
102
+ return;
103
+ }
104
+
105
+ const stateChanges = [];
106
+
107
+ for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
108
+ const entityIndex = DesignCore.Scene.selectionManager.selectionSet.selectionSet[i];
109
+ const item = DesignCore.Scene.entities.get(entityIndex);
110
+ const mirroredPoints = this.getMirroredPoints(item.points, pt1, pt2);
111
+
112
+ if (this.eraseSource) {
113
+ // Replace the original with its mirrored version
114
+ const updates = { points: mirroredPoints };
115
+ if (item.direction !== undefined) updates.direction = -item.direction;
116
+ const stateChange = new UpdateState(item, updates);
117
+ stateChanges.push(stateChange);
118
+ } else {
119
+ // Keep the original and add a mirrored copy
120
+ const copyOfItem = Utils.cloneObject(item);
121
+ copyOfItem.setProperty('points', mirroredPoints);
122
+ if (copyOfItem.direction !== undefined) copyOfItem.setProperty('direction', -copyOfItem.direction);
123
+ const stateChange = new AddState(copyOfItem);
124
+ stateChanges.push(stateChange);
125
+ }
126
+ }
127
+
128
+ DesignCore.Scene.commit(stateChanges);
129
+ }
130
+
131
+ /**
132
+ * Get mirrored points across the line defined by pt1 and pt2
133
+ * @param {Array} points
134
+ * @param {Point} pt1 - first point on the mirror line
135
+ * @param {Point} pt2 - second point on the mirror line
136
+ * @return {Array} mirroredPoints
137
+ */
138
+ getMirroredPoints(points, pt1, pt2) {
139
+ return points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).mirror(pt1, pt2));
140
+ }
141
+ }