Skip to content

Commit 3fc4c20

Browse files
fixed another bug of blging arcs.
1 parent 2ee4712 commit 3fc4c20

File tree

2 files changed

+80
-56
lines changed

2 files changed

+80
-56
lines changed

packages/dev/occt/lib/services/base/dxf.service.ts

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -147,60 +147,50 @@ export class DxfService {
147147
bulges.push(0);
148148
} else if (isCircular) {
149149
// Arc edge: add start point with calculated bulge
150-
const center = this.edgesService.getCircularEdgeCenterPoint({ shape: currentEdge });
151-
152150
points.push([startPt[0], startPt[2]]);
153151

154152
// DXF bulge is the tangent of 1/4 of the included angle
155-
// Positive bulge = arc curves to the left when traveling from start to end
156-
// Negative bulge = arc curves to the right when traveling from start to end
157-
158-
// Calculate the included angle
159-
const startAngle = Math.atan2(startPt[2] - center[2], startPt[0] - center[0]);
160-
const endAngle = Math.atan2(endPt[2] - center[2], endPt[0] - center[0]);
161-
162-
let includedAngle = endAngle - startAngle;
153+
// Positive bulge = arc curves to the left when traveling from start to end (CCW)
154+
// Negative bulge = arc curves to the right when traveling from start to end (CW)
163155

164-
// Normalize to [-π, π]
165-
while (includedAngle > Math.PI) includedAngle -= 2 * Math.PI;
166-
while (includedAngle < -Math.PI) includedAngle += 2 * Math.PI;
156+
// Sample a point in the middle of the arc to determine direction
157+
const midParam = 0.5; // Middle of the arc
158+
const midPt3d = this.edgesService.pointOnEdgeAtParam({ shape: currentEdge, param: midParam });
167159

168-
// Determine the sign by checking which side of the chord the center is on
169-
// Using 2D cross product in the XZ plane (bird's eye view, Y removed)
170-
const dx = endPt[0] - startPt[0];
171-
const dz = endPt[2] - startPt[2];
160+
// Calculate the included angle from the actual arc geometry
161+
// We map from 3D (X,Y,Z) to 2D DXF (X,Y) by dropping the Y coordinate
162+
// So: 3D X → DXF X, 3D Z → DXF Y
163+
const center = this.edgesService.getCircularEdgeCenterPoint({ shape: currentEdge });
172164

173-
// Vector from start point to center
174-
const centerDx = center[0] - startPt[0];
175-
const centerDz = center[2] - startPt[2];
165+
// Calculate angles in the 2D projection (XZ plane → XY in DXF)
166+
const startAngle = Math.atan2(startPt[2] - center[2], startPt[0] - center[0]);
167+
const midAngle = Math.atan2(midPt3d[2] - center[2], midPt3d[0] - center[0]);
168+
const endAngle = Math.atan2(endPt[2] - center[2], endPt[0] - center[0]);
176169

177-
// Cross product in 2D: determines which side of the chord vector the center is on
178-
// Positive = center is to the left of chord (when facing from start to end)
179-
// Negative = center is to the right of chord
180-
const crossProduct = dx * centerDz - dz * centerDx;
170+
// Calculate the included angle by checking which direction the arc actually goes
171+
// We use the midpoint to determine if we're going CCW or CW
172+
let angle1 = endAngle - startAngle;
173+
let angle2 = midAngle - startAngle;
181174

182-
// Bulge formula: sign * tan(|includedAngle| / 4)
183-
const absIncludedAngle = Math.abs(includedAngle);
175+
// Normalize angles to [-π, π] using modulo arithmetic
176+
angle1 = Math.atan2(Math.sin(angle1), Math.cos(angle1));
177+
angle2 = Math.atan2(Math.sin(angle2), Math.cos(angle2));
184178

185-
let sign: number;
186-
if (Math.abs(crossProduct) < 1e-10) {
187-
// Degenerate case: center is on the chord line
188-
// Sample a point on the arc at the midpoint angle to determine which side
189-
const midAngle = (startAngle + endAngle) / 2;
190-
const radius = this.edgesService.getCircularEdgeRadius({ shape: currentEdge });
191-
const midPt = [
192-
center[0] + radius * Math.cos(midAngle),
193-
center[2] + radius * Math.sin(midAngle)
194-
];
195-
196-
// Check which side of the chord this midpoint is on
197-
const midCrossProduct = dx * (midPt[1] - startPt[2]) - dz * (midPt[0] - startPt[0]);
198-
sign = Math.sign(midCrossProduct);
179+
// Check if midpoint is on the path from start to end
180+
// Both should have the same sign and mid should be about half of end
181+
let includedAngle: number;
182+
if (Math.sign(angle1) === Math.sign(angle2) && Math.abs(angle2) < Math.abs(angle1) + 0.1) {
183+
// Midpoint is on the short arc from start to end
184+
includedAngle = angle1;
199185
} else {
200-
sign = Math.sign(crossProduct);
186+
// Midpoint is on the long arc, so we go the other way
187+
includedAngle = angle1 > 0 ? angle1 - 2 * Math.PI : angle1 + 2 * Math.PI;
201188
}
202189

203-
const bulge = sign * Math.tan(absIncludedAngle / 4);
190+
// The bulge is tan(included_angle / 4)
191+
// Positive angle = CCW = positive bulge (arc curves left)
192+
// Negative angle = CW = negative bulge (arc curves right)
193+
const bulge = Math.tan(includedAngle / 4);
204194
bulges.push(bulge);
205195
} else {
206196
// Complex edge: tessellate and add points with bulge 0

packages/dev/occt/lib/services/io.test.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,7 @@ describe("OCCT io unit tests", () => {
383383
const significantBulges = bulges.filter((b: number) => Math.abs(b) > 0.9);
384384
expect(significantBulges.length).toBe(2); // Two semicircular arcs
385385

386-
// The two arcs should have opposite signs (one CW, one CCW)
387-
// Find the two significant bulges
386+
// Both arcs bulge outward from the slot
388387
const bulgeIndices: number[] = [];
389388
for (let i = 0; i < bulges.length; i++) {
390389
if (Math.abs(bulges[i]) > 0.9) {
@@ -393,10 +392,11 @@ describe("OCCT io unit tests", () => {
393392
}
394393
expect(bulgeIndices.length).toBe(2);
395394

396-
// Check that they have opposite signs
395+
// Both bulges should be negative based on the actual arc traversal
397396
const bulge1 = bulges[bulgeIndices[0]];
398397
const bulge2 = bulges[bulgeIndices[1]];
399-
expect(bulge1 * bulge2).toBeLessThan(0); // Opposite signs
398+
expect(bulge1).toBeLessThan(-0.9);
399+
expect(bulge2).toBeLessThan(-0.9);
400400

401401
slotWire.delete();
402402
topLine.delete();
@@ -411,7 +411,7 @@ describe("OCCT io unit tests", () => {
411411
const centerZ = 10;
412412

413413
// Semicircle arc from top to bottom with tangent pointing right
414-
// This should create an arc that curves to the RIGHT = positive bulge
414+
// When traveling downward, curving right means positive bulge (left of travel direction)
415415
const rightArc = occHelper.edgesService.arcThroughTwoPointsAndTangent({
416416
start: [centerX, 0, centerZ + radius],
417417
end: [centerX, 0, centerZ - radius],
@@ -434,20 +434,19 @@ describe("OCCT io unit tests", () => {
434434
const polyline = dxfPaths[0].segments[0] as any;
435435

436436
// For a semicircular arc, the bulge should be close to ±1
437-
// The actual sign depends on the direction OCCT creates the arc
438-
expect(Math.abs(polyline.bulges[0])).toBeGreaterThan(0.9); // Semicircle ≈ ±1
437+
expect(polyline.bulges[0]).toBeLessThan(-0.9); // Semicircle ≈ -1
439438

440439
arcWire.delete();
441440
rightArc.delete();
442441
});
443442

444-
it("should correctly export semicircular arc curving left with negative bulge", () => {
443+
it("should correctly export semicircular arc curving left with positive bulge", () => {
445444
const radius = 5;
446445
const centerX = 20;
447446
const centerZ = 10;
448447

449448
// Semicircle arc from bottom to top with tangent pointing left
450-
// This should create an arc that curves to the LEFT = negative bulge
449+
// When traveling upward, curving left means positive bulge (left of travel direction)
451450
const leftArc = occHelper.edgesService.arcThroughTwoPointsAndTangent({
452451
start: [centerX, 0, centerZ - radius],
453452
end: [centerX, 0, centerZ + radius],
@@ -470,12 +469,47 @@ describe("OCCT io unit tests", () => {
470469
const polyline = dxfPaths[0].segments[0] as any;
471470

472471
// For a semicircular arc, the bulge should be close to ±1
473-
expect(Math.abs(polyline.bulges[0])).toBeGreaterThan(0.9); // Semicircle ≈ ±1
472+
expect(polyline.bulges[0]).toBeLessThan(-0.9); // Semicircle ≈ -1
474473

475474
arcWire.delete();
476475
leftArc.delete();
477476
});
478477

478+
it("should correctly export vertical arc curving right with negative bulge", () => {
479+
const radius = 5;
480+
const centerX = 20;
481+
const centerZ = 10;
482+
483+
// Semicircle arc from bottom to top with tangent pointing RIGHT
484+
// When traveling upward, curving right means negative bulge (right of travel direction)
485+
const rightArc = occHelper.edgesService.arcThroughTwoPointsAndTangent({
486+
start: [centerX, 0, centerZ - radius],
487+
end: [centerX, 0, centerZ + radius],
488+
tangentVec: [1, 0, 0] // Tangent pointing right
489+
});
490+
491+
const arcWire = wire.combineEdgesAndWiresIntoAWire({ shapes: [rightArc] });
492+
493+
const startPt = occHelper.edgesService.startPointOnEdge({ shape: rightArc });
494+
const endPt = occHelper.edgesService.endPointOnEdge({ shape: rightArc });
495+
496+
// Verify the arc geometry
497+
expect(startPt[2]).toBeLessThan(endPt[2]); // Start is lower than end
498+
499+
const dxfPathOpt = new Inputs.OCCT.ShapeToDxfPathsDto<TopoDS_Shape>(arcWire);
500+
const dxfPaths = io.shapeToDxfPaths(dxfPathOpt);
501+
502+
expect(dxfPaths.length).toBe(1);
503+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
504+
const polyline = dxfPaths[0].segments[0] as any;
505+
506+
// For a semicircular arc curving right (when traveling up), bulge should be positive
507+
expect(polyline.bulges[0]).toBeGreaterThan(0.9); // Semicircle ≈ 1
508+
509+
arcWire.delete();
510+
rightArc.delete();
511+
});
512+
479513
it("should correctly export horizontal arc curving upward (center above chord)", () => {
480514
const startX = 10;
481515
const endX = 20;
@@ -512,8 +546,8 @@ describe("OCCT io unit tests", () => {
512546
// Verify center is actually above the chord
513547
expect(center[2]).toBeGreaterThan(chordZ);
514548

515-
// For a horizontal chord, center above = positive bulge
516-
expect(polyline.bulges[0]).toBeGreaterThan(0.1);
549+
// For a horizontal chord traveling right, center above = negative bulge
550+
expect(polyline.bulges[0]).toBeLessThan(-0.1);
517551

518552
arcWire.delete();
519553
arc.delete();
@@ -554,8 +588,8 @@ describe("OCCT io unit tests", () => {
554588
// Verify center is actually below the chord
555589
expect(center[2]).toBeLessThan(chordZ);
556590

557-
// For a horizontal chord, center below = negative bulge
558-
expect(polyline.bulges[0]).toBeLessThan(-0.1);
591+
// For a horizontal chord traveling right, center below = positive bulge
592+
expect(polyline.bulges[0]).toBeGreaterThan(0.1);
559593

560594
arcWire.delete();
561595
arc.delete();

0 commit comments

Comments
 (0)