feat(brep): S13-B IGES export + format utilities
Add IGES v5.3 export for B-Rep models with: - Entity mapping: Point(116), Line(110), Circular Arc(100), B-Spline Curve(126), Plane(108), B-Spline Surface(128), Vertex(502), Edge(504), Loop(508), Face(510), Shell(514), Manifold Solid B-Rep(186) - Auto-detection of circular arcs (degree-2 NURBS with isosceles control triangle) and planes (1x1 degree surfaces) - Fixed 80-column IGES format with S/G/D/P/T sections - Header-only format_utils.h for IGES/STEP number formatting New files: - include/vde/foundation/format_utils.h - include/vde/brep/iges_export.h - src/brep/iges_export.cpp - tests/brep/test_iges_export.cpp
This commit is contained in:
@@ -143,6 +143,8 @@ add_library(vde_brep STATIC
|
||||
brep/modeling.cpp
|
||||
brep/step_export.cpp
|
||||
brep/step_import.cpp
|
||||
brep/iges_import.cpp
|
||||
brep/iges_export.cpp
|
||||
brep/brep_boolean.cpp
|
||||
brep/brep_validate.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,647 @@
|
||||
#include "vde/brep/iges_export.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/foundation/format_utils.h"
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace vde::brep {
|
||||
|
||||
using namespace vde::foundation;
|
||||
using curves::NurbsCurve;
|
||||
using curves::NurbsSurface;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
namespace {
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// IgesWriter — internal IGES file builder
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
class IgesWriter {
|
||||
public:
|
||||
std::string write(const std::vector<BrepModel>& bodies);
|
||||
|
||||
private:
|
||||
// ── Section line buffers ──
|
||||
std::vector<std::string> start_lines_;
|
||||
std::vector<std::string> global_lines_;
|
||||
std::vector<std::string> de_lines_; // each is a formatted 80-col DE line
|
||||
std::vector<std::string> pd_lines_; // each is a formatted 80-col PD line
|
||||
|
||||
int de_seq_ = 0; // next DE sequence number
|
||||
int pd_seq_ = 0; // next PD line sequence number
|
||||
|
||||
// ── Map entity identity → DE sequence number ──
|
||||
std::map<int, int> vert_de_; // TopoVertex.id → DE
|
||||
std::map<int, int> edge_de_; // TopoEdge.id → DE
|
||||
std::map<int, int> loop_de_; // TopoLoop.id → DE
|
||||
std::map<int, int> face_de_; // TopoFace.id → DE
|
||||
std::map<int, int> shell_de_; // TopoShell.id → DE
|
||||
std::map<int, int> body_de_; // TopoBody.id → DE
|
||||
std::map<int, int> curve_de_; // curve pointer → DE
|
||||
std::map<int, int> surf_de_; // surface id → DE
|
||||
std::map<int, int> point_de_; // vertex index → point DE
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────
|
||||
int next_de() { return ++de_seq_; }
|
||||
int next_pd() { return ++pd_seq_; }
|
||||
|
||||
// Append an 8-char right-justified integer field
|
||||
static std::string field_i(int v, int width = 8) {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%*d", width, v);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// Append a DE line
|
||||
int append_de(int entity_type, int pd_start, int pd_count,
|
||||
int line_font = 1, int level = 1) {
|
||||
int seq = next_de();
|
||||
std::string line;
|
||||
line += field_i(entity_type); // 1-8
|
||||
line += field_i(pd_start); // 9-16 PD start line number
|
||||
line += field_i(0); // 17-24 structure
|
||||
line += field_i(line_font); // 25-32 line font
|
||||
line += field_i(level); // 33-40 level
|
||||
line += field_i(0); // 41-48 view
|
||||
line += field_i(0); // 49-56 transform
|
||||
line += field_i(0); // 57-64 label
|
||||
line += field_i(0); // 65-72 status
|
||||
// 73-80: sequence + 'D'
|
||||
char rhs[9];
|
||||
std::snprintf(rhs, sizeof(rhs), "%c%07d", 'D', seq);
|
||||
line += std::string(rhs);
|
||||
de_lines_.push_back(line);
|
||||
return seq;
|
||||
}
|
||||
|
||||
// Append parameter data for one entity; return PD start line number.
|
||||
// Splits long parameter strings across multiple 64-char lines.
|
||||
int append_pd(const std::string& params, int de_sequence) {
|
||||
int start = pd_seq_ + 1;
|
||||
std::string remaining = params;
|
||||
// Remove trailing comma if present
|
||||
if (!remaining.empty() && remaining.back() == ',')
|
||||
remaining.pop_back();
|
||||
|
||||
while (!remaining.empty()) {
|
||||
int seq = next_pd();
|
||||
std::string chunk;
|
||||
if (remaining.size() <= 64) {
|
||||
chunk = remaining;
|
||||
remaining.clear();
|
||||
} else {
|
||||
// Split at 64 or at last comma within 64
|
||||
size_t split = 64;
|
||||
size_t comma = remaining.rfind(',', 63);
|
||||
if (comma != std::string::npos && comma > 0)
|
||||
split = comma + 1; // include the comma
|
||||
chunk = remaining.substr(0, split);
|
||||
remaining = remaining.substr(split);
|
||||
// If continued line starts with comma, trim it
|
||||
if (!remaining.empty() && remaining[0] == ',')
|
||||
remaining = remaining.substr(1);
|
||||
}
|
||||
|
||||
// Chunk → cols 1-64, DE pointer → cols 65-72, P-sequence → cols 73-80
|
||||
if (chunk.size() < 64) chunk.resize(64, ' ');
|
||||
// Append DE pointer (right-justified, 8 chars)
|
||||
char de_ptr[9];
|
||||
std::snprintf(de_ptr, sizeof(de_ptr), "%8d", de_sequence);
|
||||
chunk += std::string(de_ptr);
|
||||
// Append PXXXXXXX (8 chars)
|
||||
char rhs[9];
|
||||
std::snprintf(rhs, sizeof(rhs), "P%07d", seq);
|
||||
chunk += std::string(rhs);
|
||||
|
||||
pd_lines_.push_back(chunk);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
// ── Geometry entity writers ──────────────────────────
|
||||
|
||||
/// Type 116: Point
|
||||
int write_point(const Point3D& p) {
|
||||
std::string params = fmt_iges_real(p.x()) + ","
|
||||
+ fmt_iges_real(p.y()) + ","
|
||||
+ fmt_iges_real(p.z());
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(116, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 110: Line
|
||||
int write_line(const Point3D& p1, const Point3D& p2) {
|
||||
std::string params = fmt_iges_real(p1.x()) + "," + fmt_iges_real(p1.y())
|
||||
+ "," + fmt_iges_real(p1.z()) + ","
|
||||
+ fmt_iges_real(p2.x()) + "," + fmt_iges_real(p2.y())
|
||||
+ "," + fmt_iges_real(p2.z());
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(110, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 100: Circular Arc
|
||||
/// IGES places arc in Z=ZT plane; ZT → Z offset, then 6 params for X,Y of center/start/end
|
||||
int write_arc(double zt, const Point3D& center_2d,
|
||||
const Point3D& start_2d, const Point3D& end_2d) {
|
||||
std::string params = fmt_iges_real(zt) + ","
|
||||
+ fmt_iges_real(center_2d.x()) + "," + fmt_iges_real(center_2d.y()) + ","
|
||||
+ fmt_iges_real(start_2d.x()) + "," + fmt_iges_real(start_2d.y()) + ","
|
||||
+ fmt_iges_real(end_2d.x()) + "," + fmt_iges_real(end_2d.y());
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(100, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 126: Rational B-Spline Curve
|
||||
int write_bspline_curve(const NurbsCurve& curve) {
|
||||
auto& knots = curve.knots();
|
||||
auto& cp = curve.control_points();
|
||||
auto& w = curve.weights();
|
||||
int K = static_cast<int>(cp.size()) - 1; // upper index
|
||||
int M = curve.degree();
|
||||
int N = K + 1; // number of CPs (= order for clamped)
|
||||
|
||||
// Properties: planar, open, rational/polynomial, nonperiodic
|
||||
int prop1 = 0; // nonplanar (conservative)
|
||||
int prop2 = 0; // open
|
||||
int prop3 = (!w.empty() &&
|
||||
std::any_of(w.begin(), w.end(), [](double wt){ return std::abs(wt - 1.0) > 1e-10; }))
|
||||
? 1 : 0; // 0=polynomial, 1=rational
|
||||
int prop4 = 0; // nonperiodic
|
||||
|
||||
// Domain
|
||||
double tmin = knots.empty() ? 0.0 : knots.front();
|
||||
double tmax = knots.empty() ? 1.0 : knots.back();
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << K << "," << M << "," << prop1 << "," << prop2 << "," << prop3 << "," << prop4 << ",";
|
||||
|
||||
// Knot sequence: M + K + 1 values
|
||||
for (const auto& k : knots)
|
||||
ss << fmt_iges_real(k) << ",";
|
||||
|
||||
// Weights: all N values (rational or not)
|
||||
if (!w.empty()) {
|
||||
for (const auto& wt : w)
|
||||
ss << fmt_iges_real(wt) << ",";
|
||||
} else {
|
||||
for (int i = 0; i < N; ++i)
|
||||
ss << "1.0D+00,";
|
||||
}
|
||||
|
||||
// Control points: N * 3 values (X,Y,Z triples)
|
||||
for (const auto& pt : cp)
|
||||
ss << fmt_iges_real(pt.x()) << "," << fmt_iges_real(pt.y()) << "," << fmt_iges_real(pt.z()) << ",";
|
||||
|
||||
// Parameter range
|
||||
ss << fmt_iges_real(tmin) << "," << fmt_iges_real(tmax) << ",";
|
||||
|
||||
// Unit normal (0,0,0 for non-planar)
|
||||
ss << "0.0D+00,0.0D+00,0.0D+00";
|
||||
|
||||
int pd_start = append_pd(ss.str(), de_seq_ + 1);
|
||||
return append_de(126, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 108: Plane (bounded)
|
||||
int write_plane(const NurbsSurface& surf) {
|
||||
// Get 3 points on the surface to compute plane equation
|
||||
Point3D p00 = surf.evaluate(0.0, 0.0);
|
||||
Point3D p10 = surf.evaluate(1.0, 0.0);
|
||||
Point3D p01 = surf.evaluate(0.0, 1.0);
|
||||
Vector3D n = (p10 - p00).cross(p01 - p00);
|
||||
double len = n.norm();
|
||||
if (len < 1e-12) n = Vector3D::UnitZ();
|
||||
else n /= len;
|
||||
|
||||
double A = n.x(), B = n.y(), C = n.z();
|
||||
double D = -(A * p00.x() + B * p00.y() + C * p00.z());
|
||||
|
||||
std::string params = fmt_iges_real(A) + "," + fmt_iges_real(B) + ","
|
||||
+ fmt_iges_real(C) + "," + fmt_iges_real(D) + ",0";
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(108, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 128: Rational B-Spline Surface
|
||||
int write_bspline_surface(const NurbsSurface& surf) {
|
||||
auto& cp = surf.control_points();
|
||||
auto& w = surf.weights();
|
||||
const auto& ku = surf.knots_u();
|
||||
const auto& kv = surf.knots_v();
|
||||
int du = surf.degree_u(), dv = surf.degree_v();
|
||||
int nu = static_cast<int>(cp.size());
|
||||
int nv = cp.empty() ? 0 : static_cast<int>(cp[0].size());
|
||||
|
||||
int K1 = nu - 1, K2 = nv - 1;
|
||||
int M1 = du, M2 = dv;
|
||||
|
||||
// Properties
|
||||
int prop1 = 0, prop2 = 0; // open in both u and v
|
||||
int prop3 = (!w.empty() &&
|
||||
std::any_of(w.begin(), w.end(), [](const auto& row) {
|
||||
return std::any_of(row.begin(), row.end(),
|
||||
[](double wt){ return std::abs(wt - 1.0) > 1e-10; });
|
||||
})) ? 1 : 0;
|
||||
int prop4 = 0;
|
||||
|
||||
double umin = ku.empty() ? 0.0 : ku.front();
|
||||
double umax = ku.empty() ? 1.0 : ku.back();
|
||||
double vmin = kv.empty() ? 0.0 : kv.front();
|
||||
double vmax = kv.empty() ? 1.0 : kv.back();
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << K1 << "," << K2 << "," << M1 << "," << M2 << ","
|
||||
<< prop1 << "," << prop2 << "," << prop3 << "," << prop4 << ",";
|
||||
|
||||
// U knots: M1 + K1 + 1
|
||||
for (const auto& k : ku)
|
||||
ss << fmt_iges_real(k) << ",";
|
||||
|
||||
// V knots: M2 + K2 + 1
|
||||
for (const auto& k : kv)
|
||||
ss << fmt_iges_real(k) << ",";
|
||||
|
||||
// Weights: (K1+1)*(K2+1) values, u-major
|
||||
if (!w.empty()) {
|
||||
for (int i = 0; i < nu; ++i)
|
||||
for (int j = 0; j < nv; ++j)
|
||||
ss << fmt_iges_real(w[i][j]) << ",";
|
||||
} else {
|
||||
for (int i = 0; i < nu * nv; ++i)
|
||||
ss << "1.0D+00,";
|
||||
}
|
||||
|
||||
// Control points: (K1+1)*(K2+1)*3 values, u-major
|
||||
for (int i = 0; i < nu; ++i)
|
||||
for (int j = 0; j < nv; ++j)
|
||||
ss << fmt_iges_real(cp[i][j].x()) << ","
|
||||
<< fmt_iges_real(cp[i][j].y()) << ","
|
||||
<< fmt_iges_real(cp[i][j].z()) << ",";
|
||||
|
||||
// Parameter ranges
|
||||
ss << fmt_iges_real(umin) << "," << fmt_iges_real(umax) << ","
|
||||
<< fmt_iges_real(vmin) << "," << fmt_iges_real(vmax);
|
||||
|
||||
int pd_start = append_pd(ss.str(), de_seq_ + 1);
|
||||
return append_de(128, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
// ── Detection helpers ────────────────────────────────
|
||||
|
||||
bool is_circular_arc(const NurbsCurve& curve,
|
||||
Point3D& center, double& radius,
|
||||
Vector3D& normal) {
|
||||
if (curve.degree() != 2) return false;
|
||||
auto& cp = curve.control_points();
|
||||
if (cp.size() != 3) return false;
|
||||
|
||||
// Check weights: [1, w, 1] where w = cos(theta/2)
|
||||
auto& w = curve.weights();
|
||||
if (!w.empty()) {
|
||||
if (std::abs(w[0] - 1.0) > 1e-8) return false;
|
||||
if (std::abs(w[2] - 1.0) > 1e-8) return false;
|
||||
}
|
||||
|
||||
// Control triangle: P0-P1 and P2-P1 should be equal length (isosceles)
|
||||
Vector3D v0 = cp[1] - cp[0];
|
||||
Vector3D v1 = cp[1] - cp[2];
|
||||
double d0 = v0.norm();
|
||||
double d1 = v1.norm();
|
||||
if (std::abs(d0 - d1) > std::max(d0, d1) * 1e-6) return false;
|
||||
|
||||
// Compute center as intersection of perpendicular bisectors
|
||||
Vector3D mid0 = (cp[0] + cp[1]) * 0.5;
|
||||
Vector3D mid1 = (cp[2] + cp[1]) * 0.5;
|
||||
|
||||
// Plane normal
|
||||
normal = (cp[1] - cp[0]).cross(cp[2] - cp[0]);
|
||||
double nl = normal.norm();
|
||||
if (nl < 1e-12) return false;
|
||||
normal /= nl;
|
||||
|
||||
// Perpendicular bisectors (in plane)
|
||||
Vector3D perp0 = normal.cross(v0).normalized();
|
||||
Vector3D perp1 = normal.cross(v1).normalized();
|
||||
|
||||
// Intersection of lines: mid0 + t*perp0 = mid1 + s*perp1
|
||||
// Solve 2D projection onto plane basis
|
||||
Vector3D bx = v0.normalized();
|
||||
Vector3D by = normal.cross(bx).normalized();
|
||||
|
||||
auto proj2d = [&](const Point3D& p) {
|
||||
return Point3D(p.dot(bx), p.dot(by), 0);
|
||||
};
|
||||
auto unproj = [&](const Point3D& p) {
|
||||
return Point3D(p.x() * bx + p.y() * by);
|
||||
};
|
||||
|
||||
Point3D m0_2d = proj2d(mid0);
|
||||
Point3D m1_2d = proj2d(mid1);
|
||||
Point3D d0_2d = proj2d(perp0);
|
||||
Point3D d1_2d = proj2d(perp1);
|
||||
|
||||
// Solve: m0_2d + t*d0_2d.x = m1_2d + s*d1_2d.x (in x)
|
||||
double denom = d0_2d.x() * d1_2d.y() - d0_2d.y() * d1_2d.x();
|
||||
if (std::abs(denom) < 1e-14) return false;
|
||||
double t = ((m1_2d.x() - m0_2d.x()) * d1_2d.y() -
|
||||
(m1_2d.y() - m0_2d.y()) * d1_2d.x()) / denom;
|
||||
|
||||
center = unproj(Point3D(m0_2d.x() + t * d0_2d.x(), m0_2d.y() + t * d0_2d.y(), 0));
|
||||
radius = (cp[0] - center).norm();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_plane(const NurbsSurface& surf) const {
|
||||
if (surf.degree_u() != 1 || surf.degree_v() != 1) return false;
|
||||
auto [nu, nv] = surf.num_control_points();
|
||||
return (nu == 2 && nv == 2);
|
||||
}
|
||||
|
||||
// ── Topology entity writers ──────────────────────────
|
||||
|
||||
/// Type 502: Vertex
|
||||
int write_vertex(int point_de) {
|
||||
std::string params = std::to_string(point_de);
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(502, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 504: Edge
|
||||
int write_edge(int curve_de, int v_start_de, int v_end_de) {
|
||||
std::string params = std::to_string(curve_de) + ","
|
||||
+ std::to_string(v_start_de) + ","
|
||||
+ std::to_string(v_end_de);
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(504, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 508: Loop
|
||||
int write_loop(const std::vector<int>& edge_des, const std::vector<int>& orientations) {
|
||||
std::ostringstream ss;
|
||||
ss << edge_des.size();
|
||||
// For each edge: type(1=edge), edge DE, orientation(0=forward), param_count(0)
|
||||
for (size_t i = 0; i < edge_des.size(); ++i) {
|
||||
int orient = (i < orientations.size()) ? orientations[i] : 0;
|
||||
ss << "," << "1," << edge_des[i] << "," << orient << ",0";
|
||||
}
|
||||
// Number of index groups (same as edge count)
|
||||
ss << "," << edge_des.size();
|
||||
int pd_start = append_pd(ss.str(), de_seq_ + 1);
|
||||
return append_de(508, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 510: Face
|
||||
int write_face(int surface_de, const std::vector<int>& loop_des, bool sense) {
|
||||
std::ostringstream ss;
|
||||
ss << surface_de << "," << (sense ? 1 : 0) << "," << loop_des.size();
|
||||
for (int ld : loop_des)
|
||||
ss << "," << "1," << ld << "," << (ld == loop_des[0] ? 1 : 0) << ",0";
|
||||
int pd_start = append_pd(ss.str(), de_seq_ + 1);
|
||||
return append_de(510, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 514: Shell
|
||||
int write_shell(const std::vector<int>& face_des) {
|
||||
std::ostringstream ss;
|
||||
ss << face_des.size();
|
||||
for (int fd : face_des)
|
||||
ss << "," << fd;
|
||||
int pd_start = append_pd(ss.str(), de_seq_ + 1);
|
||||
return append_de(514, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
/// Type 186: Manifold Solid B-Rep Object
|
||||
int write_manifold_solid_brep(int shell_de) {
|
||||
std::string params = std::to_string(shell_de) + ",0HNAME,0";
|
||||
int pd_start = append_pd(params, de_seq_ + 1);
|
||||
return append_de(186, pd_start, pd_seq_ - pd_start + 1);
|
||||
}
|
||||
|
||||
// ── Curve curve writing (line / arc / bspline dispatch) ──
|
||||
|
||||
int write_curve(const NurbsCurve& curve) {
|
||||
// Line detection: degree 1, 2 CPs
|
||||
if (curve.degree() == 1 && curve.control_points().size() == 2) {
|
||||
auto& cp = curve.control_points();
|
||||
return write_line(cp[0], cp[1]);
|
||||
}
|
||||
|
||||
// Circular arc detection
|
||||
Point3D center; double radius; Vector3D normal;
|
||||
if (is_circular_arc(curve, center, radius, normal)) {
|
||||
auto& cp = curve.control_points();
|
||||
Point3D start_pt = cp[0];
|
||||
Point3D end_pt = cp[2];
|
||||
|
||||
// Build local coordinate frame to get 2D projections
|
||||
Vector3D nx = (start_pt - center).normalized();
|
||||
Vector3D ny = normal.cross(nx).normalized();
|
||||
|
||||
auto to_2d = [&](const Point3D& p) -> Point3D {
|
||||
Vector3D d = p - center;
|
||||
return Point3D(d.dot(nx), d.dot(ny), 0);
|
||||
};
|
||||
|
||||
Point3D c2d = to_2d(center); // should be (0,0,0)
|
||||
Point3D s2d = to_2d(start_pt);
|
||||
Point3D e2d = to_2d(end_pt);
|
||||
|
||||
// ZT = projection of center onto normal
|
||||
double zt = center.dot(normal);
|
||||
|
||||
return write_arc(zt, c2d, s2d, e2d);
|
||||
}
|
||||
|
||||
return write_bspline_curve(curve);
|
||||
}
|
||||
|
||||
int write_surface(const NurbsSurface& surf) {
|
||||
if (is_plane(surf))
|
||||
return write_plane(surf);
|
||||
return write_bspline_surface(surf);
|
||||
}
|
||||
|
||||
// ── Process a single body ────────────────────────────
|
||||
|
||||
void process_body(const BrepModel& body) {
|
||||
// Phase 1: Write point entities for all vertices
|
||||
// Map vertex id → point DE sequence
|
||||
for (size_t vi = 0; vi < body.num_vertices(); ++vi) {
|
||||
auto& v = body.vertex(static_cast<int>(vi));
|
||||
int pt_de = write_point(v.point);
|
||||
point_de_[v.id] = pt_de;
|
||||
}
|
||||
|
||||
// Phase 2: Write curve entities for edges
|
||||
// Map edge index → curve DE sequence
|
||||
std::map<int, int> curve_des; // edge index → curve DE
|
||||
for (size_t ei = 0; ei < body.num_edges(); ++ei) {
|
||||
auto& e = body.edge(static_cast<int>(ei));
|
||||
if (e.curve) {
|
||||
int cde = write_curve(*e.curve);
|
||||
curve_des[ei] = cde;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Write surface entities for faces
|
||||
// Map surface id → surface DE sequence
|
||||
std::map<int, int> surf_des; // surface id → surface DE
|
||||
for (size_t fi = 0; fi < body.num_faces(); ++fi) {
|
||||
auto& f = body.face(static_cast<int>(fi));
|
||||
int sid = f.surface_id;
|
||||
if (surf_des.find(sid) == surf_des.end()) {
|
||||
if (sid >= 0 && static_cast<size_t>(sid) < body.num_surfaces()) {
|
||||
surf_des[sid] = write_surface(body.surface(sid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Write topology — vertex, edge, loop, face, shell, MSBO
|
||||
// Vertex topology entities
|
||||
std::map<int, int> vert_topo_de; // vertex id → vertex entity DE
|
||||
for (size_t vi = 0; vi < body.num_vertices(); ++vi) {
|
||||
auto& v = body.vertex(static_cast<int>(vi));
|
||||
int pt_de = point_de_.count(v.id) ? point_de_[v.id] : 0;
|
||||
vert_topo_de[v.id] = write_vertex(pt_de);
|
||||
}
|
||||
|
||||
// Edge topology entities
|
||||
std::map<int, int> edge_topo_de; // edge id → edge entity DE
|
||||
for (size_t ei = 0; ei < body.num_edges(); ++ei) {
|
||||
auto& e = body.edge(static_cast<int>(ei));
|
||||
int cde = curve_des.count(static_cast<int>(ei)) ? curve_des[static_cast<int>(ei)] : 0;
|
||||
int vs = vert_topo_de.count(e.v_start) ? vert_topo_de[e.v_start] : 0;
|
||||
int ve = vert_topo_de.count(e.v_end) ? vert_topo_de[e.v_end] : 0;
|
||||
edge_topo_de[e.id] = write_edge(cde, vs, ve);
|
||||
}
|
||||
|
||||
// Loop entities
|
||||
std::map<int, int> loop_topo_de; // loop id → loop entity DE
|
||||
for (const auto& loop : body.all_loops()) {
|
||||
std::vector<int> e_des;
|
||||
std::vector<int> orientations;
|
||||
for (int ei : loop.edges) {
|
||||
if (edge_topo_de.count(ei)) {
|
||||
e_des.push_back(edge_topo_de[ei]);
|
||||
// Check edge orientation
|
||||
for (size_t eidx = 0; eidx < body.num_edges(); ++eidx) {
|
||||
auto& e = body.edge(static_cast<int>(eidx));
|
||||
if (e.id == ei) {
|
||||
orientations.push_back(e.reversed ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!e_des.empty())
|
||||
loop_topo_de[loop.id] = write_loop(e_des, orientations);
|
||||
}
|
||||
|
||||
// Face entities
|
||||
std::map<int, int> face_topo_de; // face id → face entity DE
|
||||
for (size_t fi = 0; fi < body.num_faces(); ++fi) {
|
||||
auto& f = body.face(static_cast<int>(fi));
|
||||
int sd = surf_des.count(f.surface_id) ? surf_des[f.surface_id] : 0;
|
||||
std::vector<int> ld_list;
|
||||
for (int li : f.loops) {
|
||||
if (loop_topo_de.count(li))
|
||||
ld_list.push_back(loop_topo_de[li]);
|
||||
}
|
||||
if (!ld_list.empty()) {
|
||||
face_topo_de[f.id] = write_face(sd, ld_list, !f.reversed);
|
||||
}
|
||||
}
|
||||
|
||||
// Shell entity
|
||||
std::vector<int> face_list;
|
||||
for (size_t fi = 0; fi < body.num_faces(); ++fi) {
|
||||
auto& f = body.face(static_cast<int>(fi));
|
||||
if (face_topo_de.count(f.id))
|
||||
face_list.push_back(face_topo_de[f.id]);
|
||||
}
|
||||
|
||||
if (!face_list.empty()) {
|
||||
int sh_de = write_shell(face_list);
|
||||
write_manifold_solid_brep(sh_de);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// ── Build sections from collected lines ──────────────
|
||||
void build_sections() {
|
||||
// Start section
|
||||
start_lines_.push_back("ViewDesignEngine IGES Export");
|
||||
start_lines_.push_back("Generated by VDE B-Rep → IGES v5.3");
|
||||
|
||||
// Global section
|
||||
global_lines_.push_back("1H,,1H;,12HVDENGINE.IGS,53HViewDesignEngine B-Rep IGES Export,"
|
||||
"32,38,6,308,15,,1.0,1,2HIN,32768,0.016,"
|
||||
"13H2026-07-24.001,0.01,10000.0,,,11,0,12HUNKNOWN");
|
||||
}
|
||||
};
|
||||
|
||||
// ── Writer::write — entry point ─────────────────────────
|
||||
|
||||
std::string IgesWriter::write(const std::vector<BrepModel>& bodies) {
|
||||
// Build header sections
|
||||
build_sections();
|
||||
|
||||
// Process each body
|
||||
for (const auto& body : bodies) {
|
||||
if (body.num_vertices() > 0)
|
||||
process_body(body);
|
||||
}
|
||||
|
||||
// Assembly
|
||||
std::ostringstream out;
|
||||
|
||||
// Start section
|
||||
for (size_t i = 0; i < start_lines_.size(); ++i)
|
||||
out << format_iges_line(start_lines_[i], 'S', static_cast<int>(i + 1));
|
||||
|
||||
// Global section
|
||||
for (size_t i = 0; i < global_lines_.size(); ++i)
|
||||
out << format_iges_line(global_lines_[i], 'G', static_cast<int>(i + 1));
|
||||
|
||||
// Directory Entry section
|
||||
for (const auto& line : de_lines_)
|
||||
out << line << "\n";
|
||||
|
||||
// Parameter Data section
|
||||
for (const auto& line : pd_lines_)
|
||||
out << line << "\n";
|
||||
|
||||
// Terminate section — S count, G count, D count, P count, padded to 72
|
||||
std::ostringstream term_ss;
|
||||
term_ss << 'S' << std::setw(7) << std::setfill('0') << start_lines_.size()
|
||||
<< 'G' << std::setw(7) << std::setfill('0') << global_lines_.size()
|
||||
<< 'D' << std::setw(7) << std::setfill('0') << de_lines_.size()
|
||||
<< 'P' << std::setw(7) << std::setfill('0') << pd_lines_.size();
|
||||
out << format_iges_line(term_ss.str(), 'T', 1);
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Public API
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
std::string export_iges(const std::vector<BrepModel>& bodies) {
|
||||
IgesWriter writer;
|
||||
return writer.write(bodies);
|
||||
}
|
||||
|
||||
void export_iges_file(const std::string& filepath, const std::vector<BrepModel>& bodies) {
|
||||
std::ofstream out(filepath);
|
||||
if (!out) return;
|
||||
out << export_iges(bodies);
|
||||
}
|
||||
|
||||
} // namespace vde::brep
|
||||
Reference in New Issue
Block a user