UNPKG

@bitbybit-dev/occt

Version:

Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel. Run in Node and in Browser.

282 lines (281 loc) 13.2 kB
export class DxfService { constructor(base, shapeGettersService, edgesService, wiresService) { this.base = base; this.shapeGettersService = shapeGettersService; this.edgesService = edgesService; this.wiresService = wiresService; } /** * Step 1: Convert OCCT shape to DXF paths (without layer/color info) * This analyzes the shape geometry and creates appropriate DXF segments */ shapeToDxfPaths(inputs) { // Get all wires from the shape const wires = this.shapeGettersService.getWires({ shape: inputs.shape }); const paths = []; wires.forEach(wire => { // Get edges along the wire in order with consistent direction const edges = this.edgesService.getEdgesAlongWire({ shape: wire }); if (edges.length === 0) { return; } // Check if the entire wire is closed once at the beginning const isWireClosed = this.wiresService.isWireClosed({ shape: wire }); const segments = []; let i = 0; while (i < edges.length) { const edge = edges[i]; // Check if edge is a full circle - handle separately if (this.edgesService.isEdgeCircular({ shape: edge })) { const bounds = this.edgesService.getEdgeBounds(edge); const angleRange = bounds.uMax - bounds.uMin; const isFullCircle = Math.abs(angleRange - 2 * Math.PI) < 0.01; if (isFullCircle) { const circle = this.edgesService.getCircularEdgeCenterPoint({ shape: edge }); const radius = this.edgesService.getCircularEdgeRadius({ shape: edge }); segments.push({ center: [circle[0], circle[2]], radius: radius }); i++; continue; } } // Try to collect all edges (linear, arc, and complex) into a unified polyline with bulges const polylineResult = this.tryCreateUnifiedPolyline(edges, i, inputs, isWireClosed && i === 0); if (polylineResult) { segments.push(polylineResult.polyline); i = polylineResult.nextIndex; continue; } // Fallback: shouldn't reach here normally i++; } // Create path from segments if (segments.length > 0) { paths.push({ segments: segments }); } }); return paths; } /** * Step 2: Add layer and color information to DXF paths * Takes paths from shapeToDxfPaths and adds styling */ dxfPathsWithLayer(inputs) { return { layer: inputs.layer, color: inputs.color, paths: inputs.paths }; } /** * Step 3: Assemble multiple path parts into a complete DXF file * Takes multiple outputs from dxfPathsWithLayer and creates final DXF */ dxfCreate(inputs) { const model = { dxfPathsParts: inputs.pathsParts }; const dxfContent = this.base.io.dxf.dxfCreate(model); return dxfContent; } /** * Try to create a unified polyline from consecutive edges (linear, arc, and complex) * This creates a single LWPOLYLINE with bulges where applicable */ tryCreateUnifiedPolyline(edges, startIndex, inputs, shouldBeClosed) { const points = []; const bulges = []; let j = startIndex; while (j < edges.length) { const currentEdge = edges[j]; const isLinear = this.edgesService.isEdgeLinear({ shape: currentEdge }); const isCircular = this.edgesService.isEdgeCircular({ shape: currentEdge }); // Check if it's a full circle - stop here if (isCircular) { const bounds = this.edgesService.getEdgeBounds(currentEdge); const angleRange = bounds.uMax - bounds.uMin; if (Math.abs(angleRange - 2 * Math.PI) < 0.01) { break; // Full circle, handle separately } } const startPt = this.edgesService.startPointOnEdge({ shape: currentEdge }); const endPt = this.edgesService.endPointOnEdge({ shape: currentEdge }); if (isLinear) { // Linear edge: add start point with bulge 0 points.push([startPt[0], startPt[2]]); bulges.push(0); } else if (isCircular) { // Arc edge: add start point with calculated bulge points.push([startPt[0], startPt[2]]); // DXF bulge is the tangent of 1/4 of the included angle // Positive bulge = arc curves to the left when traveling from start to end (CCW) // Negative bulge = arc curves to the right when traveling from start to end (CW) // Sample a point in the middle of the arc to determine direction const midParam = 0.5; // Middle of the arc const midPt3d = this.edgesService.pointOnEdgeAtParam({ shape: currentEdge, param: midParam }); // Calculate the included angle from the actual arc geometry // We map from 3D (X,Y,Z) to 2D DXF (X,Y) by dropping the Y coordinate // So: 3D X → DXF X, 3D Z → DXF Y const center = this.edgesService.getCircularEdgeCenterPoint({ shape: currentEdge }); // Calculate angles in the 2D projection (XZ plane → XY in DXF) const startAngle = Math.atan2(startPt[2] - center[2], startPt[0] - center[0]); const midAngle = Math.atan2(midPt3d[2] - center[2], midPt3d[0] - center[0]); const endAngle = Math.atan2(endPt[2] - center[2], endPt[0] - center[0]); // Calculate the included angle by checking which direction the arc actually goes // We use the midpoint to determine if we're going CCW or CW let angle1 = endAngle - startAngle; let angle2 = midAngle - startAngle; // Normalize angles to [-π, π] using modulo arithmetic angle1 = Math.atan2(Math.sin(angle1), Math.cos(angle1)); angle2 = Math.atan2(Math.sin(angle2), Math.cos(angle2)); // Check if midpoint is on the path from start to end // Both should have the same sign and mid should be about half of end let includedAngle; if (Math.sign(angle1) === Math.sign(angle2) && Math.abs(angle2) < Math.abs(angle1) + 0.1) { // Midpoint is on the short arc from start to end includedAngle = angle1; } else { // Midpoint is on the long arc, so we go the other way includedAngle = angle1 > 0 ? angle1 - 2 * Math.PI : angle1 + 2 * Math.PI; } // The bulge is tan(included_angle / 4) // Positive angle = CCW = positive bulge (arc curves left) // Negative angle = CW = negative bulge (arc curves right) const bulge = Math.tan(includedAngle / 4); bulges.push(bulge); } else { // Complex edge: tessellate and add points with bulge 0 const points3d = this.edgesService.edgeToPoints({ shape: currentEdge, angularDeflection: inputs.angularDeflection, curvatureDeflection: inputs.curvatureDeflection, minimumOfPoints: inputs.minimumOfPoints, uTolerance: inputs.uTolerance, minimumLength: inputs.minimumLength }); // Add all tessellation points except the last one for all edges // The last point of each edge will be the start point of the next edge // For closed wires, the last edge's last point equals the first edge's first point for (let k = 0; k < points3d.length - 1; k++) { points.push([points3d[k][0], points3d[k][2]]); bulges.push(0); } } j++; } // Add the final endpoint for open polylines if (j > startIndex && !shouldBeClosed) { const lastEdge = edges[j - 1]; const endPt = this.edgesService.endPointOnEdge({ shape: lastEdge }); points.push([endPt[0], endPt[2]]); bulges.push(0); } // Return null if we didn't process any edges if (j <= startIndex || points.length < 2) { return null; } const polyline = { points: points, closed: shouldBeClosed, bulges: bulges }; return { polyline: polyline, nextIndex: j }; } /** * Try to create a polyline with bulges from consecutive line/arc segments * Returns the polyline and next index, or null if not applicable */ tryCreatePolylineWithBulges(edges, startIndex, shouldBeClosed) { const edge = edges[startIndex]; // Must start with either a line or an arc (not a full circle) const isLinear = this.edgesService.isEdgeLinear({ shape: edge }); const isCircular = this.edgesService.isEdgeCircular({ shape: edge }); if (!isLinear && !isCircular) { return null; // Complex edge, can't create polyline with bulges } // Check if it's a full circle if (isCircular) { const bounds = this.edgesService.getEdgeBounds(edge); const angleRange = bounds.uMax - bounds.uMin; const isFullCircle = Math.abs(angleRange - 2 * Math.PI) < 0.01; if (isFullCircle) { return null; // Full circle, handle separately } } // Collect consecutive linear and arc edges const points = []; const bulges = []; let j = startIndex; while (j < edges.length) { const currentEdge = edges[j]; const isCurrentLinear = this.edgesService.isEdgeLinear({ shape: currentEdge }); const isCurrentCircular = this.edgesService.isEdgeCircular({ shape: currentEdge }); // Stop if we hit a complex edge if (!isCurrentLinear && !isCurrentCircular) { break; } // Stop if we hit a full circle if (isCurrentCircular) { const bounds = this.edgesService.getEdgeBounds(currentEdge); const angleRange = bounds.uMax - bounds.uMin; if (Math.abs(angleRange - 2 * Math.PI) < 0.01) { break; } } const startPt = this.edgesService.startPointOnEdge({ shape: currentEdge }); points.push([startPt[0], startPt[2]]); // XZ plane if (isCurrentLinear) { // Linear segment: bulge = 0 bulges.push(0); } else { // Arc segment: calculate bulge const endPt = this.edgesService.endPointOnEdge({ shape: currentEdge }); const center = this.edgesService.getCircularEdgeCenterPoint({ shape: currentEdge }); // Calculate the included angle using the actual arc geometry const startAngle = Math.atan2(startPt[2] - center[2], startPt[0] - center[0]); const endAngle = Math.atan2(endPt[2] - center[2], endPt[0] - center[0]); let includedAngle = endAngle - startAngle; // Normalize to [-2π, 2π] while (includedAngle > Math.PI) includedAngle -= 2 * Math.PI; while (includedAngle < -Math.PI) includedAngle += 2 * Math.PI; // Bulge = tan(included_angle / 4) const bulge = Math.tan(includedAngle / 4); bulges.push(bulge); } j++; } // Add the end point of the last edge if (j > startIndex) { const lastEdge = edges[j - 1]; const endPt = this.edgesService.endPointOnEdge({ shape: lastEdge }); points.push([endPt[0], endPt[2]]); // XZ plane bulges.push(0); // Last vertex doesn't need a bulge } // Only create polyline with bulges if we have at least 2 edges or if it's beneficial if (j - startIndex < 2) { return null; // Not enough edges to justify polyline } const polyline = { points: points, closed: shouldBeClosed, bulges: bulges }; return { polyline: polyline, nextIndex: j }; } }