@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,162 @@
1
+ import { Tool } from './tool.js';
2
+ import { Strings } from '../lib/strings.js';
3
+ import { Constants } from '../lib/constants.js';
4
+ import { BasePolyline } from '../entities/basePolyline.js';
5
+ import { RemoveState, UpdateState } from '../lib/stateManager.js';
6
+ import { CornerEntity } from './cornerEntity.js';
7
+ import { Intersection } from '../lib/intersect.js';
8
+
9
+ import { DesignCore } from '../designCore.js';
10
+
11
+ /**
12
+ * ChamferFilletBase Class
13
+ * Base class for Chamfer and Fillet commands, providing shared entity selection
14
+ * state, segment resolution, and sharp-trim logic.
15
+ * @extends Tool
16
+ */
17
+ export class ChamferFilletBase extends Tool {
18
+ /** Create a ChamferFilletBase command */
19
+ constructor() {
20
+ super();
21
+ this.firstPick = new CornerEntity();
22
+ this.secondPick = new CornerEntity();
23
+ // Shared corner geometry computed by resolveCornerGeometry() before action()
24
+ // If the lines form a cross there are four possible locations.
25
+ // The click points are used to determine which corner to operate on.
26
+ this.intersectionPoint = null;
27
+ }
28
+
29
+ /**
30
+ * Compute the corner geometry shared by Fillet and Chamfer and store it on the instance.
31
+ * Validates that both resolved segments are Lines, finds the virtual intersection
32
+ * of the two infinite lines, and derives click-side directions and kept endpoints.
33
+ * Returns false and notifies the user on any invalid configuration.
34
+ * @param {string} noEntityMsg - message shown when a resolved segment is not a Line
35
+ * @return {boolean}
36
+ */
37
+ resolveCornerGeometry(noEntityMsg) {
38
+ if (!this.firstPick.resolveEndpoints()) {
39
+ DesignCore.Core.notify(`${this.type} - ${this.firstPick.entity.type} ${noEntityMsg}`);
40
+ return false;
41
+ }
42
+ if (!this.secondPick.resolveEndpoints()) {
43
+ DesignCore.Core.notify(`${this.type} - ${this.secondPick.entity.type} ${noEntityMsg}`);
44
+ return false;
45
+ }
46
+
47
+ const result = Intersection.intersectSegmentSegment(
48
+ this.firstPick.lineStart, this.firstPick.lineEnd,
49
+ this.secondPick.lineStart, this.secondPick.lineEnd,
50
+ true, true,
51
+ );
52
+
53
+ // Reject parallel, collinear, or overlapping segments — no unique corner exists.
54
+ const status = result.status;
55
+ if (status === Intersection.Status.PARALLEL ||
56
+ status === Intersection.Status.OVERLAPPING ||
57
+ status === Intersection.Status.COINCIDENT) {
58
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
59
+ return false;
60
+ }
61
+
62
+ // Guard against near-parallel directions that pass the status check but
63
+ // produce an unreliable intersection point far from both segments.
64
+ const dir1 = this.firstPick.lineEnd.subtract(this.firstPick.lineStart);
65
+ const dir2 = this.secondPick.lineEnd.subtract(this.secondPick.lineStart);
66
+ const cross = dir1.cross(dir2);
67
+ const len1 = dir1.length();
68
+ const len2 = dir2.length();
69
+ if (len1 === 0 || len2 === 0 || Math.abs(cross) / (len1 * len2) < Constants.Tolerance.EPSILON) {
70
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
71
+ return false;
72
+ }
73
+
74
+ this.intersectionPoint = result.points[0] || null;
75
+
76
+ if (!this.intersectionPoint) {
77
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
78
+ return false;
79
+ }
80
+
81
+ if (this.firstPick.clickDistance(this.intersectionPoint) < Constants.Tolerance.EPSILON ||
82
+ this.secondPick.clickDistance(this.intersectionPoint) < Constants.Tolerance.EPSILON) {
83
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.SELECTION}`);
84
+ return false;
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Trim both entities to the sharp intersection point with no arc or chamfer line.
92
+ * Handles Line+Line, Polyline+Polyline (same entity), and Line+Polyline cases.
93
+ * Reads geometry from instance fields set by resolveCornerGeometry().
94
+ * @return {Array} - array of state changes to be committed by the caller
95
+ */
96
+ applySharpTrim() {
97
+ const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
98
+ const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
99
+
100
+ // Line + Line: trim both lines to the intersection point.
101
+ if (!firstIsPolyline && !secondIsPolyline) {
102
+ return [
103
+ new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), this.intersectionPoint] }),
104
+ new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), this.intersectionPoint] }),
105
+ ];
106
+ }
107
+
108
+ // Polyline + Polyline (same entity): replace the shared corner vertex (or open endpoints)
109
+ // with the intersection point.
110
+ if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
111
+ const lastIdx = this.firstPick.entity.points.length - 1;
112
+ const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
113
+ const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
114
+ (this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
115
+ (this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
116
+ );
117
+
118
+ const newPoints = this.firstPick.entity.points.map((p) => p.clone());
119
+
120
+ if (isOpenEnds) {
121
+ newPoints[0] = this.intersectionPoint.clone();
122
+ newPoints[lastIdx] = this.intersectionPoint.clone();
123
+ } else {
124
+ const closeSegIdx = this.firstPick.entity.points.length;
125
+ const seg1 = this.firstPick.segmentIndex;
126
+ const seg2 = this.secondPick.segmentIndex;
127
+ const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
128
+ const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
129
+ newPoints.splice(cornerIdx, 1, this.intersectionPoint.clone());
130
+ }
131
+ return [new UpdateState(this.firstPick.entity, { points: newPoints })];
132
+ }
133
+
134
+ // Line + Polyline (or Polyline + Line): consume the line into the polyline.
135
+ const poly = firstIsPolyline ? this.firstPick : this.secondPick;
136
+ const line = firstIsPolyline ? this.secondPick : this.firstPick;
137
+ const polySegIdx = poly.segmentIndex;
138
+ const keepStart = poly.keepStart(this.intersectionPoint);
139
+ let newPoints;
140
+ if (keepStart) {
141
+ // Keep the polyline points up to (not including) the selected segment start,
142
+ // trim to the intersection and append the kept end of the line.
143
+ newPoints = [
144
+ ...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
145
+ this.intersectionPoint.clone(),
146
+ line.lineKeptEnd(this.intersectionPoint).clone(),
147
+ ];
148
+ } else {
149
+ // Keep the polyline points from the selected segment start onwards,
150
+ // prepending the kept end of the line and the intersection.
151
+ newPoints = [
152
+ line.lineKeptEnd(this.intersectionPoint).clone(),
153
+ this.intersectionPoint.clone(),
154
+ ...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
155
+ ];
156
+ }
157
+ return [
158
+ new RemoveState(line.entity),
159
+ new UpdateState(poly.entity, { points: newPoints }),
160
+ ];
161
+ }
162
+ }
@@ -1,21 +1,39 @@
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
-
7
- import {DesignCore} from '../designCore.js';
8
-
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 } from '../lib/stateManager.js';
8
+
9
+ import { DesignCore } from '../designCore.js';
10
+
11
+ /**
12
+ * Copy Command Class
13
+ * @extends Tool
14
+ */
9
15
  export class Copy extends Tool {
16
+ /** Create a Copy command */
10
17
  constructor() {
11
18
  super();
12
19
  }
13
20
 
21
+ /**
22
+ * Register the command
23
+ * @return {Object}
24
+ * command = name of the command
25
+ * shortcut = shortcut for the command
26
+ * type = type to group command in toolbars (omitted if not shown)
27
+ */
14
28
  static register() {
15
- const command = {command: 'Copy', shortcut: 'CO', type: 'Tool'};
29
+ const command = { command: 'Copy', shortcut: 'CO', type: 'Tool' };
16
30
  return command;
17
31
  }
18
32
 
33
+ /**
34
+ * Execute method
35
+ * executes the workflow, requesting input required to perform the command
36
+ */
19
37
  async execute() {
20
38
  try {
21
39
  const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
@@ -26,60 +44,70 @@ export class Copy extends Tool {
26
44
 
27
45
  const op2 = new PromptOptions(Strings.Input.BASEPOINT, [Input.Type.POINT]);
28
46
  const pt1 = await DesignCore.Scene.inputManager.requestInput(op2);
47
+ if (pt1 === undefined) return;
29
48
  this.points.push(pt1);
30
49
 
31
- const op3 = new PromptOptions(Strings.Input.DESTINATION, [Input.Type.POINT, Input.Type.NUMBER]);
32
- const pt2 = await DesignCore.Scene.inputManager.requestInput(op3);
50
+ const op3 = new PromptOptions(Strings.Input.DESTINATION, [Input.Type.POINT, Input.Type.DYNAMIC]);
51
+ while (true) {
52
+ // Reset to just the base point
53
+ this.points = [this.points[0]];
54
+
55
+ // Anchor inputPoint to the base point so DYNAMIC (distance) input always
56
+ // projects relative to it, not relative to the last copied position.
57
+ DesignCore.Scene.inputManager.inputPoint = this.points[0];
33
58
 
34
- if (Input.getType(pt2) === Input.Type.POINT) {
35
- this.points.push(pt2);
36
- } else if (Input.getType(pt2) === Input.Type.NUMBER) {
37
- const basePoint = this.points.at(-1);
38
- const angle = Utils.degrees2radians(DesignCore.Mouse.inputAngle());
39
- const point = basePoint.project(angle, pt2);
40
- this.points.push(point);
59
+ const pt2 = await DesignCore.Scene.inputManager.requestInput(op3);
60
+ if (pt2 === undefined) break;
61
+ this.points[1] = pt2;
62
+ DesignCore.Scene.inputManager.actionCommand();
41
63
  }
42
64
 
43
65
  DesignCore.Scene.inputManager.executeCommand();
44
- } catch (error) {
66
+ } catch (err) {
45
67
  Logging.instance.error(`${this.type} - ${err}`);
46
68
  }
47
69
  }
48
70
 
71
+ /**
72
+ * Preview the command during execution
73
+ */
49
74
  preview() {
50
75
  if (this.points.length >= 1) {
51
76
  const mousePoint = DesignCore.Mouse.pointOnScene();
52
77
 
53
78
  // Draw a line
54
79
  const points = [this.points.at(-1), mousePoint];
80
+ DesignCore.Scene.tempEntities.create('Line', { points: points });
55
81
 
56
- DesignCore.Scene.createTempItem('Line', {points: points});
82
+ const delta = mousePoint.subtract(this.lastMousePoint || this.points[0]);
83
+ this.lastMousePoint = mousePoint;
57
84
 
58
- const xDelta = mousePoint.x - this.points[0].x;
59
- const yDelta = mousePoint.y - this.points[0].y;
60
-
61
- for (let i = 0; i <DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
62
- for (let j = 0; j <DesignCore.Scene.selectionManager.selectedItems[i].points.length; j++) {
63
- DesignCore.Scene.selectionManager.selectedItems[i].points[j].x = DesignCore.Scene.items[DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]].points[j].x + xDelta;
64
- DesignCore.Scene.selectionManager.selectedItems[i].points[j].y = DesignCore.Scene.items[DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]].points[j].y + yDelta;
65
- }
85
+ for (let i = 0; i < DesignCore.Scene.selectionManager.selectedItems.length; i++) {
86
+ const item = DesignCore.Scene.selectionManager.selectedItems[i];
87
+ const offsetPoints = item.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
88
+ item.setProperty('points', offsetPoints);
66
89
  }
67
90
  }
68
91
  }
69
92
 
93
+ /**
94
+ * Perform the command
95
+ */
70
96
  action() {
71
- const xDelta = this.points[1].x - this.points[0].x;
72
- const yDelta = this.points[1].y - this.points[0].y;
73
-
74
- for (let i = 0; i <DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
75
- const copyofitem = Utils.cloneObject(DesignCore.Scene.items[DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]]);
76
-
77
- for (let j = 0; j < copyofitem.points.length; j++) {
78
- copyofitem.points[j].x = DesignCore.Scene.items[DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]].points[j].x + xDelta;
79
- copyofitem.points[j].y = DesignCore.Scene.items[DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]].points[j].y + yDelta;
80
- }
81
-
82
- DesignCore.Scene.items.push(copyofitem);
97
+ // points[1] is cleared before each destination prompt
98
+ // executeCommand() calls action() - guard on missing points and duplication
99
+ if (!this.points[1]) return;
100
+ const delta = this.points[1].subtract(this.points[0]);
101
+ const stateChanges = [];
102
+
103
+ for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
104
+ const copyofitem = Utils.cloneObject(DesignCore.Scene.entities.get(DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]));
105
+ const offsetPoints = copyofitem.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
106
+ copyofitem.setProperty('points', offsetPoints);
107
+ const stateChange = new AddState(copyofitem);
108
+ stateChanges.push(stateChange);
83
109
  }
110
+
111
+ DesignCore.Scene.commit(stateChanges);
84
112
  };
85
113
  }
@@ -0,0 +1,65 @@
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
+
6
+ import { DesignCore } from '../designCore.js';
7
+
8
+ /**
9
+ * Copybase Command Class
10
+ * @extends Tool
11
+ */
12
+ export class Copybase extends Tool {
13
+ /** Create a Copybase command */
14
+ constructor() {
15
+ super();
16
+ }
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
+ */
25
+ static register() {
26
+ const command = { command: 'Copybase', shortcut: 'COPYBASE' };
27
+ return command;
28
+ }
29
+
30
+ /**
31
+ * Execute method
32
+ * executes the workflow, requesting input required to perform the command
33
+ */
34
+ async execute() {
35
+ try {
36
+ const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
37
+
38
+ if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
39
+ await DesignCore.Scene.inputManager.requestInput(op);
40
+ }
41
+
42
+ const op1 = new PromptOptions(Strings.Input.BASEPOINT, [Input.Type.POINT]);
43
+ const pt0 = await DesignCore.Scene.inputManager.requestInput(op1);
44
+ this.points.push(pt0);
45
+
46
+ DesignCore.Scene.inputManager.executeCommand();
47
+ } catch (err) {
48
+ Logging.instance.error(`${this.type} - ${err}`);
49
+ }
50
+ }
51
+
52
+
53
+ /**
54
+ * Perform the command
55
+ */
56
+ action() {
57
+ // generate clipboard data from selected items
58
+ const selectedItems = DesignCore.Scene.selectionManager.selectedItems;
59
+
60
+ // create clipboard data
61
+ DesignCore.Clipboard.Entities = selectedItems;
62
+ // set base point
63
+ DesignCore.Clipboard.BasePoint = this.points[0];
64
+ }
65
+ }
@@ -0,0 +1,59 @@
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
+
6
+ import { DesignCore } from '../designCore.js';
7
+
8
+ /**
9
+ * Copyclip Command Class
10
+ * @extends Tool
11
+ */
12
+ export class Copyclip extends Tool {
13
+ /** Create a Copyclip command */
14
+ constructor() {
15
+ super();
16
+ }
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
+ */
25
+ static register() {
26
+ const command = { command: 'Copyclip', shortcut: 'COPYCLIP' };
27
+ return command;
28
+ }
29
+
30
+ /**
31
+ * Execute method
32
+ * executes the workflow, requesting input required to perform the command
33
+ */
34
+ async execute() {
35
+ try {
36
+ const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
37
+
38
+ if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
39
+ await DesignCore.Scene.inputManager.requestInput(op);
40
+ }
41
+
42
+ DesignCore.Scene.inputManager.executeCommand();
43
+ } catch (err) {
44
+ Logging.instance.error(`${this.type} - ${err}`);
45
+ }
46
+ }
47
+
48
+
49
+ /**
50
+ * Perform the command
51
+ */
52
+ action() {
53
+ // generate clipboard data from selected items
54
+ const selectedItems = DesignCore.Scene.selectionManager.selectedItems;
55
+
56
+ // create clipboard data
57
+ DesignCore.Clipboard.Entities = selectedItems;
58
+ }
59
+ }
@@ -0,0 +1,138 @@
1
+ import { Line } from '../entities/line.js';
2
+ import { BasePolyline } from '../entities/basePolyline.js';
3
+
4
+ import { DesignCore } from '../designCore.js';
5
+
6
+ /**
7
+ * CornerEntity
8
+ * Holds the per-side state for one of the two entities involved in a Chamfer or Fillet operation.
9
+ */
10
+ export class CornerEntity {
11
+ /** Create a CornerEntity */
12
+ constructor() {
13
+ // Set during execute() when the user selects an entity
14
+ this.entity = null;
15
+ this.clickPoint = null;
16
+ // For polyline selections: the resolved Line segment and its 1-based index
17
+ this.segment = null;
18
+ this.segmentIndex = null;
19
+ // Geometry set by resolveCornerGeometry() before action()
20
+ this.lineStart = null;
21
+ this.lineEnd = null;
22
+ }
23
+ /**
24
+ * The direction vector of the line segment (lineEnd − lineStart).
25
+ * @return {Point}
26
+ */
27
+ get direction() {
28
+ return this.lineEnd.subtract(this.lineStart);
29
+ }
30
+
31
+ /**
32
+ * The active line segment: the resolved polyline segment, or the entity itself for a plain Line.
33
+ * @return {Line|null}
34
+ */
35
+ get activeSeg() {
36
+ return this.segment ?? this.entity;
37
+ }
38
+
39
+ /**
40
+ * Validate that the active segment is a Line and populate lineStart and lineEnd from it.
41
+ * Returns false if the active segment is not a Line.
42
+ * @return {boolean}
43
+ */
44
+ resolveEndpoints() {
45
+ if (!(this.activeSeg instanceof Line)) return false;
46
+ this.lineStart = this.activeSeg.points[0];
47
+ this.lineEnd = this.activeSeg.points[1];
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Set the entity and click point, then resolve the polyline segment.
53
+ * For polylines, populates segment and segmentIndex from the closest segment.
54
+ * For plain Lines, segment and segmentIndex are cleared to null.
55
+ * Returns false and notifies the user if the closest polyline segment is an arc.
56
+ * @param {object} entity - the selected entity
57
+ * @param {Point} clickPoint - the point where the user clicked
58
+ * @param {string} arcSegmentErrorMsg - notification string shown when the segment is an arc
59
+ * @return {boolean}
60
+ */
61
+ setPick(entity, clickPoint, arcSegmentErrorMsg) {
62
+ this.entity = entity;
63
+ this.clickPoint = clickPoint;
64
+ if (!(this.entity instanceof BasePolyline)) {
65
+ this.segment = null;
66
+ this.segmentIndex = null;
67
+ return true;
68
+ }
69
+ const index = this.entity.getClosestSegmentIndex(this.clickPoint);
70
+ const segment = this.entity.getClosestSegment(this.clickPoint);
71
+ if (!(segment instanceof Line)) {
72
+ DesignCore.Core.notify(arcSegmentErrorMsg);
73
+ return false;
74
+ }
75
+ this.segment = segment;
76
+ this.segmentIndex = index;
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * The perpendicular foot of clickPoint onto the line defined by lineStart/lineEnd.
82
+ * @return {Point}
83
+ */
84
+ get #clickOnLine() {
85
+ return this.clickPoint.perpendicular(this.lineStart, this.lineEnd);
86
+ }
87
+
88
+ /**
89
+ * Distance from the perpendicular foot to the intersection point.
90
+ * A value below Constants.Tolerance.EPSILON indicates an ambiguous corner.
91
+ * @param {Point} intersectionPoint - the corner intersection point
92
+ * @return {number}
93
+ */
94
+ clickDistance(intersectionPoint) {
95
+ return this.#clickOnLine.distance(intersectionPoint);
96
+ }
97
+
98
+ /**
99
+ * Vector from the intersection point toward the perpendicular foot (i.e. toward the click side).
100
+ * @param {Point} intersectionPoint - the corner intersection point
101
+ * @return {Point}
102
+ */
103
+ clickDir(intersectionPoint) {
104
+ return this.#clickOnLine.subtract(intersectionPoint);
105
+ }
106
+
107
+ /**
108
+ * Unit vector in the clickDir direction.
109
+ * @param {Point} intersectionPoint - the corner intersection point
110
+ * @return {Point}
111
+ */
112
+ clickUnit(intersectionPoint) {
113
+ return this.clickDir(intersectionPoint).normalise();
114
+ }
115
+
116
+ /**
117
+ * The line endpoint on the same side of the intersection as the click.
118
+ * @param {Point} intersectionPoint - the corner intersection point
119
+ * @return {Point}
120
+ */
121
+ lineKeptEnd(intersectionPoint) {
122
+ const dir = this.clickDir(intersectionPoint);
123
+ return dir.dot(this.lineStart.subtract(intersectionPoint)) >= dir.dot(this.lineEnd.subtract(intersectionPoint)) ?
124
+ this.lineStart : this.lineEnd;
125
+ }
126
+
127
+ /**
128
+ * Determine whether the start of the polyline segment should be kept when trimming.
129
+ * @param {Point} intersectionPoint - the corner intersection point
130
+ * @return {boolean}
131
+ */
132
+ keepStart(intersectionPoint) {
133
+ const segStart = this.entity.points[this.segmentIndex - 1];
134
+ const segEnd = this.entity.points[this.segmentIndex % this.entity.points.length];
135
+ const dir = this.clickDir(intersectionPoint);
136
+ return dir.dot(segStart.subtract(intersectionPoint)) >= dir.dot(segEnd.subtract(intersectionPoint));
137
+ }
138
+ }
@@ -0,0 +1,72 @@
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
+ import { RemoveState } from '../lib/stateManager.js';
6
+
7
+ import { DesignCore } from '../designCore.js';
8
+
9
+ /**
10
+ * Cutclip Command Class
11
+ * @extends Tool
12
+ */
13
+ export class Cutclip extends Tool {
14
+ /** Create a Cutclip command */
15
+ constructor() {
16
+ super();
17
+ }
18
+
19
+ /**
20
+ * Register the command
21
+ * @return {Object}
22
+ * command = name of the command
23
+ * shortcut = shortcut for the command
24
+ * type = type to group command in toolbars (omitted if not shown)
25
+ */
26
+ static register() {
27
+ const command = { command: 'Cutclip', shortcut: 'CUTCLIP' };
28
+ return command;
29
+ }
30
+
31
+ /**
32
+ * Execute method
33
+ * executes the workflow, requesting input required to perform the command
34
+ */
35
+ async execute() {
36
+ try {
37
+ const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
38
+
39
+ if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
40
+ await DesignCore.Scene.inputManager.requestInput(op);
41
+ }
42
+
43
+ DesignCore.Scene.inputManager.executeCommand();
44
+ } catch (err) {
45
+ Logging.instance.error(`${this.type} - ${err}`);
46
+ }
47
+ }
48
+
49
+
50
+ /**
51
+ * Perform the command
52
+ */
53
+ action() {
54
+ // generate clipboard data from selected items
55
+ const selectedItems = DesignCore.Scene.selectionManager.selectedItems;
56
+
57
+ // create clipboard data
58
+ DesignCore.Clipboard.Entities = selectedItems;
59
+ // get a copy of the selection set
60
+ const selections = DesignCore.Scene.selectionManager.selectionSet.selectionSet.slice();
61
+ // create state changes for each of the selections
62
+ const stateChanges = [];
63
+
64
+ for (let i = 0; i < selections.length; i++) {
65
+ const stateChange = new RemoveState(DesignCore.Scene.entities.get(selections[i]));
66
+ stateChanges.push(stateChange);
67
+ }
68
+
69
+ // delete each of the selections from the scene items
70
+ DesignCore.Scene.commit(stateChanges);
71
+ }
72
+ }