@bitbybit-dev/base 0.20.2 → 0.20.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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/lib/api/inputs/base-inputs.d.ts +8 -0
  3. package/lib/api/inputs/index.d.ts +3 -0
  4. package/lib/api/inputs/index.js +3 -0
  5. package/lib/api/inputs/inputs.d.ts +3 -0
  6. package/lib/api/inputs/inputs.js +3 -0
  7. package/lib/api/inputs/line-inputs.d.ts +240 -0
  8. package/lib/api/inputs/line-inputs.js +247 -0
  9. package/lib/api/inputs/mesh-inputs.d.ts +82 -0
  10. package/lib/api/inputs/mesh-inputs.js +83 -0
  11. package/lib/api/inputs/point-inputs.d.ts +153 -0
  12. package/lib/api/inputs/point-inputs.js +188 -0
  13. package/lib/api/inputs/polyline-inputs.d.ts +206 -0
  14. package/lib/api/inputs/polyline-inputs.js +229 -0
  15. package/lib/api/inputs/text-inputs.d.ts +1 -1
  16. package/lib/api/inputs/transforms-inputs.d.ts +18 -0
  17. package/lib/api/inputs/transforms-inputs.js +29 -0
  18. package/lib/api/inputs/vector-inputs.d.ts +8 -0
  19. package/lib/api/inputs/vector-inputs.js +8 -0
  20. package/lib/api/models/index.d.ts +1 -0
  21. package/lib/api/models/index.js +1 -0
  22. package/lib/api/models/point/bucket.d.ts +1 -0
  23. package/lib/api/models/point/bucket.js +1 -0
  24. package/lib/api/models/point/hex-grid-data.d.ts +8 -0
  25. package/lib/api/models/point/hex-grid-data.js +2 -0
  26. package/lib/api/models/point/index.d.ts +1 -0
  27. package/lib/api/models/point/index.js +1 -0
  28. package/lib/api/services/dates.js +45 -15
  29. package/lib/api/services/index.d.ts +3 -0
  30. package/lib/api/services/index.js +3 -0
  31. package/lib/api/services/line.d.ts +149 -0
  32. package/lib/api/services/line.js +320 -0
  33. package/lib/api/services/lists.d.ts +1 -1
  34. package/lib/api/services/lists.js +1 -2
  35. package/lib/api/services/mesh.d.ts +66 -0
  36. package/lib/api/services/mesh.js +235 -0
  37. package/lib/api/services/point.d.ts +96 -1
  38. package/lib/api/services/point.js +540 -1
  39. package/lib/api/services/polyline.d.ts +149 -0
  40. package/lib/api/services/polyline.js +444 -0
  41. package/lib/api/services/transforms.d.ts +26 -1
  42. package/lib/api/services/transforms.js +66 -3
  43. package/lib/api/services/vector.d.ts +18 -0
  44. package/lib/api/services/vector.js +27 -0
  45. package/lib/api/unit-test-helper.d.ts +20 -0
  46. package/lib/api/unit-test-helper.js +130 -0
  47. package/package.json +2 -2
@@ -0,0 +1,149 @@
1
+ import { GeometryHelper } from "./geometry-helper";
2
+ import * as Inputs from "../inputs";
3
+ import { Point } from "./point";
4
+ import { Vector } from "./vector";
5
+ import { Line } from "./line";
6
+ /**
7
+ * Contains various methods for polyline. Polyline in bitbybit is a simple object that has points property containing an array of points.
8
+ * { points: number[][] }
9
+ */
10
+ export declare class Polyline {
11
+ private readonly vector;
12
+ private readonly point;
13
+ private readonly line;
14
+ private readonly geometryHelper;
15
+ constructor(vector: Vector, point: Point, line: Line, geometryHelper: GeometryHelper);
16
+ /**
17
+ * Gets the length of the polyline
18
+ * @param inputs a polyline
19
+ * @returns length
20
+ * @group get
21
+ * @shortname polyline length
22
+ * @drawable false
23
+ */
24
+ length(inputs: Inputs.Polyline.PolylineDto): number;
25
+ /**
26
+ * Gets the number of points in the polyline
27
+ * @param inputs a polyline
28
+ * @returns nr of points
29
+ * @group get
30
+ * @shortname nr polyline points
31
+ * @drawable false
32
+ */
33
+ countPoints(inputs: Inputs.Polyline.PolylineDto): number;
34
+ /**
35
+ * Gets the points of the polyline
36
+ * @param inputs a polyline
37
+ * @returns points
38
+ * @group get
39
+ * @shortname points
40
+ * @drawable true
41
+ */
42
+ getPoints(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Point3[];
43
+ /**
44
+ * Reverse the points of the polyline
45
+ * @param inputs a polyline
46
+ * @returns reversed polyline
47
+ * @group convert
48
+ * @shortname reverse polyline
49
+ * @drawable true
50
+ */
51
+ reverse(inputs: Inputs.Polyline.PolylineDto): Inputs.Polyline.PolylinePropertiesDto;
52
+ /**
53
+ * Transform the polyline
54
+ * @param inputs a polyline
55
+ * @returns transformed polyline
56
+ * @group transforms
57
+ * @shortname transform polyline
58
+ * @drawable true
59
+ */
60
+ transformPolyline(inputs: Inputs.Polyline.TransformPolylineDto): Inputs.Polyline.PolylinePropertiesDto;
61
+ /**
62
+ * Create the polyline
63
+ * @param inputs points and info if its closed
64
+ * @returns polyline
65
+ * @group create
66
+ * @shortname polyline
67
+ * @drawable true
68
+ */
69
+ create(inputs: Inputs.Polyline.PolylineCreateDto): Inputs.Polyline.PolylinePropertiesDto;
70
+ /**
71
+ * Create the lines from the polyline
72
+ * @param inputs polyline
73
+ * @returns lines
74
+ * @group convert
75
+ * @shortname polyline to lines
76
+ * @drawable true
77
+ */
78
+ polylineToLines(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Line3[];
79
+ /**
80
+ * Create the segments from the polyline
81
+ * @param inputs polyline
82
+ * @returns segments
83
+ * @group convert
84
+ * @shortname polyline to segments
85
+ * @drawable false
86
+ */
87
+ polylineToSegments(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Segment3[];
88
+ /**
89
+ * Finds the points of self intersection of the polyline
90
+ * @param inputs points of self intersection
91
+ * @returns polyline
92
+ * @group intersections
93
+ * @shortname polyline self intersections
94
+ * @drawable true
95
+ */
96
+ polylineSelfIntersection(inputs: Inputs.Polyline.PolylineToleranceDto): Inputs.Base.Point3[];
97
+ /**
98
+ * Finds the intersection points between two polylines
99
+ * @param inputs two polylines and tolerance
100
+ * @returns points
101
+ * @group intersection
102
+ * @shortname two polyline intersection
103
+ * @drawable true
104
+ */
105
+ twoPolylineIntersection(inputs: Inputs.Polyline.TwoPolylinesToleranceDto): Inputs.Base.Point3[];
106
+ /**
107
+ * Create the polylines from segments that are potentially connected but scrambled randomly
108
+ * @param inputs segments
109
+ * @returns polylines
110
+ * @group sort
111
+ * @shortname segments to polylines
112
+ * @drawable true
113
+ */
114
+ sortSegmentsIntoPolylines(inputs: Inputs.Polyline.SegmentsToleranceDto): Inputs.Base.Polyline3[];
115
+ /**
116
+ * Calculates the maximum possible half-line fillet radius for each corner
117
+ * of a given polyline. For a closed polyline, it includes the corners
118
+ * connecting the last segment back to the first.
119
+ *
120
+ * The calculation uses the 'half-line' constraint, meaning the fillet's
121
+ * tangent points must lie within the first half of each segment connected
122
+ * to the corner.
123
+ *
124
+ * @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
125
+ * @returns An array containing the maximum fillet radius calculated for each corner.
126
+ * The order corresponds to corners P[1]...P[n-2] for open polylines,
127
+ * and P[1]...P[n-2], P[0], P[n-1] for closed polylines.
128
+ * Returns an empty array if the polyline has fewer than 3 points.
129
+ * @group fillet
130
+ * @shortname polyline max fillet radii
131
+ * @drawable false
132
+ */
133
+ maxFilletsHalfLine(inputs: Inputs.Polyline.PolylineToleranceDto): number[];
134
+ /**
135
+ * Calculates the single safest maximum fillet radius that can be applied
136
+ * uniformly to all corners of a polyline, based on the 'half-line' constraint.
137
+ * This is determined by finding the minimum of the maximum possible fillet
138
+ * radii calculated for each individual corner.
139
+ *
140
+ * @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
141
+ * @returns The smallest value from the results of calculatePolylineMaxFillets.
142
+ * Returns 0 if the polyline has fewer than 3 points or if any
143
+ * calculated maximum radius is 0.
144
+ * @group fillet
145
+ * @shortname polyline safest fillet radius
146
+ * @drawable false
147
+ */
148
+ safestFilletRadius(inputs: Inputs.Polyline.PolylineToleranceDto): number;
149
+ }
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Contains various methods for polyline. Polyline in bitbybit is a simple object that has points property containing an array of points.
3
+ * { points: number[][] }
4
+ */
5
+ export class Polyline {
6
+ constructor(vector, point, line, geometryHelper) {
7
+ this.vector = vector;
8
+ this.point = point;
9
+ this.line = line;
10
+ this.geometryHelper = geometryHelper;
11
+ }
12
+ /**
13
+ * Gets the length of the polyline
14
+ * @param inputs a polyline
15
+ * @returns length
16
+ * @group get
17
+ * @shortname polyline length
18
+ * @drawable false
19
+ */
20
+ length(inputs) {
21
+ let distanceOfPolyline = 0;
22
+ for (let i = 1; i < inputs.polyline.points.length; i++) {
23
+ const previousPoint = inputs.polyline.points[i - 1];
24
+ const currentPoint = inputs.polyline.points[i];
25
+ distanceOfPolyline += this.point.distance({ startPoint: previousPoint, endPoint: currentPoint });
26
+ }
27
+ return distanceOfPolyline;
28
+ }
29
+ /**
30
+ * Gets the number of points in the polyline
31
+ * @param inputs a polyline
32
+ * @returns nr of points
33
+ * @group get
34
+ * @shortname nr polyline points
35
+ * @drawable false
36
+ */
37
+ countPoints(inputs) {
38
+ return inputs.polyline.points.length;
39
+ }
40
+ /**
41
+ * Gets the points of the polyline
42
+ * @param inputs a polyline
43
+ * @returns points
44
+ * @group get
45
+ * @shortname points
46
+ * @drawable true
47
+ */
48
+ getPoints(inputs) {
49
+ return inputs.polyline.points;
50
+ }
51
+ /**
52
+ * Reverse the points of the polyline
53
+ * @param inputs a polyline
54
+ * @returns reversed polyline
55
+ * @group convert
56
+ * @shortname reverse polyline
57
+ * @drawable true
58
+ */
59
+ reverse(inputs) {
60
+ return { points: inputs.polyline.points.reverse() };
61
+ }
62
+ /**
63
+ * Transform the polyline
64
+ * @param inputs a polyline
65
+ * @returns transformed polyline
66
+ * @group transforms
67
+ * @shortname transform polyline
68
+ * @drawable true
69
+ */
70
+ transformPolyline(inputs) {
71
+ const transformation = inputs.transformation;
72
+ let transformedControlPoints = inputs.polyline.points;
73
+ transformedControlPoints = this.geometryHelper.transformControlPoints(transformation, transformedControlPoints);
74
+ return { points: transformedControlPoints };
75
+ }
76
+ /**
77
+ * Create the polyline
78
+ * @param inputs points and info if its closed
79
+ * @returns polyline
80
+ * @group create
81
+ * @shortname polyline
82
+ * @drawable true
83
+ */
84
+ create(inputs) {
85
+ return {
86
+ points: inputs.points,
87
+ };
88
+ }
89
+ /**
90
+ * Create the lines from the polyline
91
+ * @param inputs polyline
92
+ * @returns lines
93
+ * @group convert
94
+ * @shortname polyline to lines
95
+ * @drawable true
96
+ */
97
+ polylineToLines(inputs) {
98
+ const segments = this.polylineToSegments(inputs);
99
+ return segments.map((segment) => ({
100
+ start: segment[0],
101
+ end: segment[1],
102
+ }));
103
+ }
104
+ /**
105
+ * Create the segments from the polyline
106
+ * @param inputs polyline
107
+ * @returns segments
108
+ * @group convert
109
+ * @shortname polyline to segments
110
+ * @drawable false
111
+ */
112
+ polylineToSegments(inputs) {
113
+ const polyline = inputs.polyline;
114
+ const segments = [];
115
+ const points = polyline.points;
116
+ const numPoints = points.length;
117
+ if (numPoints < 2) {
118
+ return segments;
119
+ }
120
+ // Create segments between consecutive points
121
+ for (let i = 0; i < numPoints - 1; i++) {
122
+ segments.push([points[i], points[i + 1]]);
123
+ }
124
+ // Add closing segment if the polyline is closed and has enough points
125
+ if (polyline.isClosed && numPoints >= 2) {
126
+ if (!this.point.twoPointsAlmostEqual({ point1: points[numPoints - 1], point2: points[0], tolerance: 1e-9 })) {
127
+ segments.push([points[numPoints - 1], points[0]]);
128
+ }
129
+ }
130
+ return segments;
131
+ }
132
+ /**
133
+ * Finds the points of self intersection of the polyline
134
+ * @param inputs points of self intersection
135
+ * @returns polyline
136
+ * @group intersections
137
+ * @shortname polyline self intersections
138
+ * @drawable true
139
+ */
140
+ polylineSelfIntersection(inputs) {
141
+ const { polyline, tolerance } = inputs;
142
+ const lines = this.polylineToLines({ polyline });
143
+ const numSegments = lines.length;
144
+ if (numSegments < 3) {
145
+ return [];
146
+ }
147
+ const selfIntersectionPoints = [];
148
+ const defaultTolerance = tolerance !== null && tolerance !== void 0 ? tolerance : 1e-6;
149
+ for (let i = 0; i < numSegments; i++) {
150
+ for (let j = i + 1; j < numSegments; j++) {
151
+ let areAdjacent = (j === i + 1);
152
+ if (!areAdjacent && polyline.isClosed && i === 0 && j === numSegments - 1) {
153
+ areAdjacent = true;
154
+ }
155
+ if (areAdjacent) {
156
+ continue;
157
+ }
158
+ const intersection = this.line.lineLineIntersection({
159
+ line1: lines[i],
160
+ line2: lines[j],
161
+ checkSegmentsOnly: true,
162
+ tolerance: defaultTolerance,
163
+ });
164
+ if (intersection) {
165
+ let foundClose = false;
166
+ for (const existingPoint of selfIntersectionPoints) {
167
+ if (this.point.twoPointsAlmostEqual({
168
+ point1: intersection,
169
+ point2: existingPoint,
170
+ tolerance: defaultTolerance
171
+ })) {
172
+ foundClose = true;
173
+ break;
174
+ }
175
+ }
176
+ if (!foundClose) {
177
+ selfIntersectionPoints.push(intersection);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return selfIntersectionPoints;
183
+ }
184
+ /**
185
+ * Finds the intersection points between two polylines
186
+ * @param inputs two polylines and tolerance
187
+ * @returns points
188
+ * @group intersection
189
+ * @shortname two polyline intersection
190
+ * @drawable true
191
+ */
192
+ twoPolylineIntersection(inputs) {
193
+ const { polyline1, polyline2, tolerance } = inputs;
194
+ const lines1 = this.polylineToLines({ polyline: polyline1 });
195
+ const lines2 = this.polylineToLines({ polyline: polyline2 });
196
+ const intersectionPoints = [];
197
+ const defaultTolerance = tolerance !== null && tolerance !== void 0 ? tolerance : 1e-6;
198
+ for (const seg1 of lines1) {
199
+ for (const seg2 of lines2) {
200
+ const intersection = this.line.lineLineIntersection({
201
+ line1: seg1,
202
+ line2: seg2,
203
+ checkSegmentsOnly: true,
204
+ tolerance: defaultTolerance,
205
+ });
206
+ if (intersection) {
207
+ let foundClose = false;
208
+ for (const existingPoint of intersectionPoints) {
209
+ if (this.point.twoPointsAlmostEqual({
210
+ point1: intersection,
211
+ point2: existingPoint,
212
+ tolerance: defaultTolerance
213
+ })) {
214
+ foundClose = true;
215
+ break;
216
+ }
217
+ }
218
+ if (!foundClose) {
219
+ intersectionPoints.push(intersection);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ return intersectionPoints;
225
+ }
226
+ /**
227
+ * Create the polylines from segments that are potentially connected but scrambled randomly
228
+ * @param inputs segments
229
+ * @returns polylines
230
+ * @group sort
231
+ * @shortname segments to polylines
232
+ * @drawable true
233
+ */
234
+ sortSegmentsIntoPolylines(inputs) {
235
+ var _a;
236
+ const tolerance = (_a = inputs.tolerance) !== null && _a !== void 0 ? _a : 1e-5; // Default tolerance
237
+ const segments = inputs.segments;
238
+ if (!segments || segments.length === 0) {
239
+ return [];
240
+ }
241
+ const toleranceSq = tolerance * tolerance;
242
+ const numSegments = segments.length;
243
+ const used = new Array(numSegments).fill(false);
244
+ const results = [];
245
+ const endpointMap = new Map();
246
+ const invTolerance = 1.0 / tolerance;
247
+ const getGridKey = (p) => {
248
+ const ix = Math.round(p[0] * invTolerance);
249
+ const iy = Math.round(p[1] * invTolerance);
250
+ const iz = Math.round(p[2] * invTolerance);
251
+ return `${ix},${iy},${iz}`;
252
+ };
253
+ // 1. Build the spatial map
254
+ for (let i = 0; i < numSegments; i++) {
255
+ const segment = segments[i];
256
+ if (this.point.twoPointsAlmostEqual({ point1: segment[0], point2: segment[1], tolerance: tolerance })) {
257
+ used[i] = true; // Mark degenerate as used
258
+ continue;
259
+ }
260
+ const key0 = getGridKey(segment[0]);
261
+ const key1 = getGridKey(segment[1]);
262
+ const info0 = { segmentIndex: i, endpointIndex: 0, coords: segment[0] };
263
+ const info1 = { segmentIndex: i, endpointIndex: 1, coords: segment[1] };
264
+ if (!endpointMap.has(key0))
265
+ endpointMap.set(key0, []);
266
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
267
+ endpointMap.get(key0).push(info0);
268
+ if (key1 !== key0) {
269
+ if (!endpointMap.has(key1))
270
+ endpointMap.set(key1, []);
271
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
272
+ endpointMap.get(key1).push(info1);
273
+ }
274
+ else {
275
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
276
+ endpointMap.get(key0).push(info1); // Add both endpoints if same key
277
+ }
278
+ }
279
+ // --- Helper to find connecting segment ---
280
+ const findConnection = (pointToMatch) => {
281
+ const searchKeys = [];
282
+ const px = Math.round(pointToMatch[0] * invTolerance);
283
+ const py = Math.round(pointToMatch[1] * invTolerance);
284
+ const pz = Math.round(pointToMatch[2] * invTolerance);
285
+ for (let dx = -1; dx <= 1; dx++) {
286
+ for (let dy = -1; dy <= 1; dy++) {
287
+ for (let dz = -1; dz <= 1; dz++) {
288
+ searchKeys.push(`${px + dx},${py + dy},${pz + dz}`);
289
+ }
290
+ }
291
+ }
292
+ let bestMatch = undefined;
293
+ let minDistanceSq = toleranceSq;
294
+ for (const searchKey of searchKeys) {
295
+ const candidates = endpointMap.get(searchKey);
296
+ if (!candidates)
297
+ continue;
298
+ for (const candidate of candidates) {
299
+ // Only consider segments not already used in *any* polyline
300
+ if (!used[candidate.segmentIndex]) {
301
+ const diffVector = this.vector.sub({ first: candidate.coords, second: pointToMatch });
302
+ const distSq = this.vector.lengthSq({ vector: diffVector });
303
+ if (distSq < minDistanceSq) {
304
+ // Check with precise method if it's a potential best match
305
+ if (this.point.twoPointsAlmostEqual({ point1: candidate.coords, point2: pointToMatch, tolerance: tolerance })) {
306
+ bestMatch = candidate;
307
+ minDistanceSq = distSq; // Update min distance found
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ // No need for final check here, already done inside the loop
314
+ if (bestMatch && !used[bestMatch.segmentIndex]) { // Double check used status
315
+ return bestMatch;
316
+ }
317
+ return undefined;
318
+ };
319
+ // 2. Iterate and chain segments
320
+ for (let i = 0; i < numSegments; i++) {
321
+ if (used[i])
322
+ continue; // Skip if already part of a polyline
323
+ // Start a new polyline
324
+ used[i] = true; // Mark the starting segment as used
325
+ const startSegment = segments[i];
326
+ const currentPoints = [startSegment[0], startSegment[1]];
327
+ let currentHead = startSegment[0];
328
+ let currentTail = startSegment[1];
329
+ let isClosed = false;
330
+ let iterations = 0;
331
+ // Extend forward (tail)
332
+ while (iterations++ < numSegments) {
333
+ const nextMatch = findConnection(currentTail);
334
+ if (!nextMatch)
335
+ break; // No unused segment connects to the tail
336
+ // We found a potential next segment
337
+ const nextSegment = segments[nextMatch.segmentIndex];
338
+ const pointToAdd = (nextMatch.endpointIndex === 0) ? nextSegment[1] : nextSegment[0];
339
+ // Check for closure *before* adding the point
340
+ if (this.point.twoPointsAlmostEqual({ point1: pointToAdd, point2: currentHead, tolerance: tolerance })) {
341
+ isClosed = true;
342
+ // Mark the closing segment as used
343
+ used[nextMatch.segmentIndex] = true;
344
+ break; // Closed loop found
345
+ }
346
+ // Not closing, so add the point and mark the segment used
347
+ used[nextMatch.segmentIndex] = true;
348
+ currentPoints.push(pointToAdd);
349
+ currentTail = pointToAdd;
350
+ }
351
+ // Extend backward (head) - only if not already closed
352
+ iterations = 0;
353
+ if (!isClosed) {
354
+ while (iterations++ < numSegments) {
355
+ const prevMatch = findConnection(currentHead);
356
+ if (!prevMatch)
357
+ break; // No unused segment connects to the head
358
+ const prevSegment = segments[prevMatch.segmentIndex];
359
+ const pointToAdd = (prevMatch.endpointIndex === 0) ? prevSegment[1] : prevSegment[0];
360
+ // Check for closure against the current tail *before* adding
361
+ if (this.point.twoPointsAlmostEqual({ point1: pointToAdd, point2: currentTail, tolerance: tolerance })) {
362
+ isClosed = true;
363
+ // Mark the closing segment as used
364
+ used[prevMatch.segmentIndex] = true;
365
+ break; // Closed loop found
366
+ }
367
+ // Not closing, add point to beginning and mark segment used
368
+ used[prevMatch.segmentIndex] = true;
369
+ currentPoints.unshift(pointToAdd);
370
+ currentHead = pointToAdd;
371
+ }
372
+ }
373
+ // Final closure check (might be redundant now, but harmless)
374
+ // This catches cases like A->B, B->A which form a 2-point closed loop
375
+ if (!isClosed && currentPoints.length >= 2) {
376
+ isClosed = this.point.twoPointsAlmostEqual({ point1: currentHead, point2: currentTail, tolerance: tolerance });
377
+ }
378
+ // Remove duplicate point for closed loops with more than 2 points
379
+ if (isClosed && currentPoints.length > 2) {
380
+ // Check if the first and last points are indeed the ones needing merging
381
+ if (this.point.twoPointsAlmostEqual({ point1: currentPoints[currentPoints.length - 1], point2: currentPoints[0], tolerance: tolerance })) {
382
+ currentPoints.pop();
383
+ }
384
+ }
385
+ // Add the completed polyline (even if it's just the starting segment)
386
+ results.push({
387
+ points: currentPoints,
388
+ isClosed: isClosed,
389
+ });
390
+ }
391
+ return results;
392
+ }
393
+ /**
394
+ * Calculates the maximum possible half-line fillet radius for each corner
395
+ * of a given polyline. For a closed polyline, it includes the corners
396
+ * connecting the last segment back to the first.
397
+ *
398
+ * The calculation uses the 'half-line' constraint, meaning the fillet's
399
+ * tangent points must lie within the first half of each segment connected
400
+ * to the corner.
401
+ *
402
+ * @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
403
+ * @returns An array containing the maximum fillet radius calculated for each corner.
404
+ * The order corresponds to corners P[1]...P[n-2] for open polylines,
405
+ * and P[1]...P[n-2], P[0], P[n-1] for closed polylines.
406
+ * Returns an empty array if the polyline has fewer than 3 points.
407
+ * @group fillet
408
+ * @shortname polyline max fillet radii
409
+ * @drawable false
410
+ */
411
+ maxFilletsHalfLine(inputs) {
412
+ return this.point.maxFilletsHalfLine({
413
+ points: inputs.polyline.points,
414
+ checkLastWithFirst: inputs.polyline.isClosed,
415
+ tolerance: inputs.tolerance,
416
+ });
417
+ }
418
+ /**
419
+ * Calculates the single safest maximum fillet radius that can be applied
420
+ * uniformly to all corners of a polyline, based on the 'half-line' constraint.
421
+ * This is determined by finding the minimum of the maximum possible fillet
422
+ * radii calculated for each individual corner.
423
+ *
424
+ * @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
425
+ * @returns The smallest value from the results of calculatePolylineMaxFillets.
426
+ * Returns 0 if the polyline has fewer than 3 points or if any
427
+ * calculated maximum radius is 0.
428
+ * @group fillet
429
+ * @shortname polyline safest fillet radius
430
+ * @drawable false
431
+ */
432
+ safestFilletRadius(inputs) {
433
+ const allMaxRadii = this.maxFilletsHalfLine(inputs);
434
+ if (allMaxRadii.length === 0) {
435
+ // No corners, or fewer than 3 points. No fillet possible.
436
+ return 0;
437
+ }
438
+ // Find the minimum radius among all calculated maximums.
439
+ // If any corner calculation resulted in 0, the safest radius is 0.
440
+ const safestRadius = Math.min(...allMaxRadii);
441
+ // Ensure we don't return a negative radius if Math.min had weird input (shouldn't happen here)
442
+ return Math.max(0, safestRadius);
443
+ }
444
+ }
@@ -74,6 +74,17 @@ export declare class Transforms {
74
74
  * @drawable false
75
75
  */
76
76
  scaleXYZ(inputs: Inputs.Transforms.ScaleXYZDto): Base.TransformMatrixes;
77
+ /**
78
+ * Creates a stretch transformation along a specific direction, relative to a center point.
79
+ * This scales points along the given direction vector while leaving points in the
80
+ * plane perpendicular to the direction (passing through the center) unchanged.
81
+ * @param inputs Defines the center, direction, and scale factor for the stretch.
82
+ * @returns Array of transformations: [Translate To Origin, Stretch, Translate Back].
83
+ * @group scale
84
+ * @shortname stretch dir center
85
+ * @drawable false
86
+ */
87
+ stretchDirFromCenter(inputs: Inputs.Transforms.StretchDirCenterDto): Base.TransformMatrixes;
77
88
  /**
78
89
  * Creates uniform scale transformation
79
90
  * @param inputs Scale Dto
@@ -110,13 +121,27 @@ export declare class Transforms {
110
121
  * @drawable false
111
122
  */
112
123
  translationsXYZ(inputs: Inputs.Transforms.TranslationsXYZDto): Base.TransformMatrixes[];
124
+ /**
125
+ * Creates the identity transformation
126
+ * @returns transformation
127
+ * @group identity
128
+ * @shortname identity
129
+ * @drawable false
130
+ */
131
+ identity(): Base.TransformMatrix;
113
132
  private translation;
114
133
  private scaling;
115
- private identity;
116
134
  private rotationAxis;
117
135
  private rotationX;
118
136
  private rotationY;
119
137
  private rotationZ;
120
138
  private rotationYawPitchRoll;
121
139
  private rotationMatrixFromQuat;
140
+ /**
141
+ * Creates a 4x4 matrix that scales along a given direction vector.
142
+ * @param direction The direction vector (will be normalized).
143
+ * @param scale The scale factor along the direction.
144
+ * @returns A 4x4 column-major transformation matrix.
145
+ */
146
+ private stretchDirection;
122
147
  }