feat(brep): S13-A — IGES import support
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 32s

Implement IGES (Initial Graphics Exchange Specification) file reader that
converts IGES entities to B-Rep models.

Key features:
- 80-character fixed-width card image parser (S/G/D/P/T sections)
- Directory Entry parsing with all 18 DE fields per entity
- Parameter Data parsing with DE pointer tracking
- D exponent notation support (e.g., 1.5D+2 = 150)

Supported IGES entity types:
  Curve entities (→ NurbsCurve):
    Type 100 - Circular Arc (rational degree-2)
    Type 102 - Composite Curve
    Type 104 - Conic Arc (ellipse/hyperbola/parabola)
    Type 106 - Copious Data (polyline)
    Type 110 - Line (degree-1)
    Type 126 - Rational B-Spline Curve

  Surface entities (→ NurbsSurface):
    Type 108 - Plane
    Type 114 - Parametric Spline Surface
    Type 118 - Ruled Surface
    Type 120 - Surface of Revolution
    Type 122 - Tabulated Cylinder (extrude)
    Type 128 - Rational B-Spline Surface
    Type 140 - Offset Surface
    Type 192 - Right Circular Cylindrical Surface
    Type 194 - Right Circular Conical Surface
    Type 196 - Spherical Surface
    Type 198 - Toroidal Surface

  Topology entities (→ BrepModel):
    Type 186 - Manifold Solid B-Rep Object
    Type 502 - Vertex
    Type 504 - Edge
    Type 508 - Loop
    Type 510 - Face
    Type 514 - Shell

Tests cover: line, arc, cylinder, sphere, cone, torus, plane,
B-spline curve/surface, ruled surface, revolution surface,
tabulated cylinder, copious data polyline, manifold solid B-Rep,
D exponent notation, empty/truncated file handling, and file errors.
This commit is contained in:
茂之钳
2026-07-24 07:37:03 +00:00
parent 4f75bb8b07
commit 6cfd186780
3 changed files with 1962 additions and 0 deletions
+1381
View File
@@ -0,0 +1,1381 @@
#include "vde/brep/iges_import.h"
#include "vde/curves/nurbs_curve.h"
#include "vde/curves/nurbs_surface.h"
#include <fstream>
#include <sstream>
#include <optional>
#include <unordered_map>
#include <vector>
#include <string>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <stdexcept>
namespace vde::brep {
using core::Point3D;
using core::Vector3D;
// ─────────────────────────────────────────────────────────────
// Error state
// ─────────────────────────────────────────────────────────────
static IgesError g_last_iges_error = IgesError::Ok;
static std::string g_last_iges_error_msg;
IgesError iges_last_error() { return g_last_iges_error; }
const std::string& iges_last_error_message() { return g_last_iges_error_msg; }
static void set_error(IgesError e, const std::string& msg) {
g_last_iges_error = e;
g_last_iges_error_msg = msg;
}
static void clear_error() { g_last_iges_error = IgesError::Ok; g_last_iges_error_msg.clear(); }
// ─────────────────────────────────────────────────────────────
// Parsed IGES entity
// ─────────────────────────────────────────────────────────────
struct IgesEntity {
int type_number = 0; // Entity type number
int pd_pointer = 0; // Line number in P section where params start
int structure = 0; // Structure (hierarchy info)
int line_font = 0; // Line font pattern number
int level = 0; // Level number
int view = 0; // View pointer
int transform_matrix = 0; // Transformation matrix pointer (0 = identity)
int label_display = 0; // Label display associativity
int status_number = 0; // Status number
int line_weight = 0; // Line weight number
int color = 0; // Color number
int param_line_count = 0; // Number of parameter data lines
int form_number = 0; // Form number (for parameterized entities)
int entity_label = 0; // Entity label pointer
int entity_subscript = 0; // Entity subscript number
std::string parameters; // Raw parameter string (split from lines)
int index = 0; // Our sequential index (DE entry position)
};
// ─────────────────────────────────────────────────────────────
// IGES Parser — 80-column fixed-width records
// ─────────────────────────────────────────────────────────────
class IgesParser {
public:
explicit IgesParser(const std::string& data);
bool parse();
const std::vector<IgesEntity>& entities() const { return entities_; }
const std::string& global_section() const { return global_section_; }
const std::string& start_section() const { return start_section_; }
private:
struct CardRecord {
char section = '\0';
int sequence = 0;
std::string fields; // columns 1-72
std::string raw; // full 80 chars
};
const std::string& data_;
std::vector<CardRecord> records_;
std::string start_section_;
std::string global_section_;
std::vector<IgesEntity> entities_;
bool split_records();
bool parse_section(char section_type, std::string& out_content);
bool parse_directory_entries(const std::vector<CardRecord>& records);
bool parse_parameter_data(const std::vector<CardRecord>& records);
static std::string strip(const std::string& s);
static double parse_real(const std::string& s);
static int parse_int(const std::string& s);
static std::vector<std::string> split_params(const std::string& s);
static std::string field(const std::string& line, int col); // 1-indexed, 8-char field
};
IgesParser::IgesParser(const std::string& data) : data_(data) {}
bool IgesParser::parse() {
if (!split_records()) return false;
// Find section boundaries
std::vector<size_t> s_bounds, g_bounds, d_bounds, p_bounds, t_bounds;
for (size_t i = 0; i < records_.size(); ++i) {
switch (records_[i].section) {
case 'S': s_bounds.push_back(i); break;
case 'G': g_bounds.push_back(i); break;
case 'D': d_bounds.push_back(i); break;
case 'P': p_bounds.push_back(i); break;
case 'T': t_bounds.push_back(i); break;
default: break;
}
}
if (s_bounds.empty() || g_bounds.empty() || d_bounds.empty() || p_bounds.empty()) {
set_error(IgesError::InvalidSection, "Missing required IGES sections");
return false;
}
// Start section
for (size_t i = s_bounds.front(); i < g_bounds.front(); ++i)
start_section_ += records_[i].fields + "\n";
// Global section
for (size_t i = g_bounds.front(); i < d_bounds.front(); ++i)
global_section_ += records_[i].fields;
// Directory entry section
std::vector<CardRecord> d_records;
for (size_t i = d_bounds.front(); (p_bounds.empty() || i < p_bounds.front()); ++i)
d_records.push_back(records_[i]);
if (!parse_directory_entries(d_records)) return false;
// Parameter data section
std::vector<CardRecord> p_records;
for (size_t i = p_bounds.front(); (t_bounds.empty() || i < t_bounds.front()); ++i)
p_records.push_back(records_[i]);
if (!parse_parameter_data(p_records)) return false;
return true;
}
bool IgesParser::split_records() {
size_t pos = 0;
int line_no = 0;
while (pos < data_.size()) {
size_t nl = data_.find('\n', pos);
if (nl == std::string::npos) nl = data_.size();
std::string line = data_.substr(pos, nl - pos);
// Strip CR
if (!line.empty() && line.back() == '\r')
line.pop_back();
++line_no;
pos = nl + 1;
// Skip blank lines
if (line.empty()) continue;
CardRecord cr;
cr.raw = line;
// Pad to 80 columns
if (line.size() < 80) line.append(80 - line.size(), ' ');
else if (line.size() > 80) line = line.substr(0, 80);
cr.section = line[72];
cr.fields = line.substr(0, 72);
// Parse sequence number (columns 74-80)
std::string seq_str = line.substr(73, 7);
cr.sequence = parse_int(seq_str);
records_.push_back(std::move(cr));
}
return true;
}
bool IgesParser::parse_directory_entries(const std::vector<CardRecord>& d_records) {
if (d_records.size() % 2 != 0) {
set_error(IgesError::ParseError, "Odd number of directory entry records");
return false;
}
for (size_t i = 0; i < d_records.size(); i += 2) {
const auto& line1 = d_records[i].fields;
const auto& line2 = d_records[i + 1].fields;
IgesEntity ent;
ent.index = static_cast<int>(i / 2);
// Line 1 fields (each 8 chars, 1-indexed columns)
ent.type_number = parse_int(field(line1, 1));
ent.pd_pointer = parse_int(field(line1, 9));
ent.structure = parse_int(field(line1, 17));
ent.line_font = parse_int(field(line1, 25));
ent.level = parse_int(field(line1, 33));
ent.view = parse_int(field(line1, 41));
ent.transform_matrix = parse_int(field(line1, 49));
ent.label_display = parse_int(field(line1, 57));
ent.status_number = parse_int(field(line1, 65));
// Line 2 fields
// Skip 1-8 (same type_number)
ent.line_weight = parse_int(field(line2, 9));
ent.color = parse_int(field(line2, 17));
ent.param_line_count = parse_int(field(line2, 25));
ent.form_number = parse_int(field(line2, 33));
// columns 41-56 are reserved
ent.entity_label = parse_int(field(line2, 57));
ent.entity_subscript = parse_int(field(line2, 65));
entities_.push_back(std::move(ent));
}
return true;
}
bool IgesParser::parse_parameter_data(const std::vector<CardRecord>& p_records) {
// Group P records by DE pointer (cols 66-72 of each line)
std::unordered_map<int, std::string> pd_by_de;
for (const auto& rec : p_records) {
// DE pointer is in cols 66-72 (1-indexed), which is position 65-71 in 0-indexed
std::string de_str = rec.fields.substr(65, 7);
int de_ptr = parse_int(de_str);
// Data is cols 1-64
pd_by_de[de_ptr] += rec.fields.substr(0, 64);
}
// Assign parameter strings to entities based on pd_pointer
for (auto& ent : entities_) {
auto it = pd_by_de.find(ent.pd_pointer);
if (it != pd_by_de.end()) {
std::string& raw = it->second;
// Strip trailing spaces and the record delimiter semicolon
while (!raw.empty() && (raw.back() == ' ' || raw.back() == '\t'))
raw.pop_back();
// The IGES P section lines end with a semicolon followed by spaces
// Find the last semicolon
size_t semi = raw.rfind(';');
if (semi != std::string::npos) {
// Also strip spaces before semicolon isn't needed — the data ends at ;
// But we want to keep text up to the semicolon or past it
// Actually params can have embedded ; in strings — but typically the
// last semicolon is the record delimiter. We'll strip trailing spaces+';'
while (!raw.empty() && (raw.back() == ' ' || raw.back() == '\t' || raw.back() == ';'))
raw.pop_back();
}
ent.parameters = raw;
}
}
return true;
}
// ─────────────────────────────────────────────────────────────
// Static helpers
// ─────────────────────────────────────────────────────────────
std::string IgesParser::strip(const std::string& s) {
size_t start = 0, end = s.size();
while (start < end && (s[start] == ' ' || s[start] == '\t')) ++start;
while (end > start && (s[end-1] == ' ' || s[end-1] == '\t')) --end;
return s.substr(start, end - start);
}
double IgesParser::parse_real(const std::string& s) {
std::string t = strip(s);
if (t.empty()) return 0.0;
// Handle D exponent notation (e.g., 1.5D+2 → 1.5e+2)
size_t dpos = t.find_first_of("Dd");
if (dpos != std::string::npos) {
t[dpos] = 'e';
}
try {
return std::stod(t);
} catch (...) {
return 0.0;
}
}
int IgesParser::parse_int(const std::string& s) {
std::string t = strip(s);
if (t.empty()) return 0;
try {
return std::stoi(t);
} catch (...) {
return 0;
}
}
std::vector<std::string> IgesParser::split_params(const std::string& s) {
std::vector<std::string> result;
std::string current;
int paren_depth = 0;
for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == ',' && paren_depth == 0) {
result.push_back(current);
current.clear();
} else {
if (c == '(' || c == '[' || c == '{') paren_depth++;
else if (c == ')' || c == ']' || c == '}') paren_depth--;
current += c;
}
}
result.push_back(current);
return result;
}
std::string IgesParser::field(const std::string& line, int col) {
// col is 1-indexed (1..64), each field is 8 chars
int start = col - 1;
if (start < 0 || start >= static_cast<int>(line.size())) return "";
return line.substr(start, 8);
}
// ─────────────────────────────────────────────────────────────
// IGES to B-Rep converter
// ─────────────────────────────────────────────────────────────
class IgesToBrep {
public:
IgesToBrep(const std::vector<IgesEntity>& entities) : entities_(entities) {}
std::vector<BrepModel> convert() {
std::vector<BrepModel> result;
// First pass: register all entities by index for easy lookup
// Second pass: find manifold solid B-Rep objects (type 186)
std::vector<int> solid_indices;
for (size_t i = 0; i < entities_.size(); ++i) {
entity_by_index_[entities_[i].index] = static_cast<int>(i);
if (entities_[i].type_number == 186) {
solid_indices.push_back(entities_[i].index);
}
}
if (!solid_indices.empty()) {
for (int si : solid_indices) {
auto body = build_manifold_solid(si);
if (body.num_faces() > 0)
result.push_back(std::move(body));
}
}
// Fallback: if no solids found, try collecting all faces/surfaces into one model
if (result.empty()) {
BrepModel model;
bool has_any = false;
// Collect all faces (type 510)
for (const auto& ent : entities_) {
if (ent.type_number == 510) {
int face_id = convert_face(ent.index, model);
if (face_id >= 0) has_any = true;
}
}
// If still no faces, collect standalone surfaces
if (!has_any) {
for (const auto& ent : entities_) {
int sid = convert_or_get_surface(ent.index, model);
if (sid >= 0) has_any = true;
}
}
if (has_any && model.num_faces() > 0) {
std::vector<int> face_ids;
for (size_t i = 0; i < model.num_faces(); ++i)
face_ids.push_back(static_cast<int>(i));
int shell_id = model.add_shell(face_ids, model.num_faces() >= 4);
model.add_body({shell_id}, "IGES_Model");
result.push_back(std::move(model));
}
}
return result;
}
private:
const std::vector<IgesEntity>& entities_;
// Index maps
std::unordered_map<int, int> entity_by_index_; // index → position in entities_
std::unordered_map<int, int> vp_cache_; // IGES DE index → BrepModel vertex index
std::unordered_map<int, int> surf_cache_; // IGES DE index → BrepModel surface index
std::unordered_map<int, curves::NurbsCurve> curve_cache_;
std::unordered_map<int, Point3D> point_cache_;
const IgesEntity* get_entity(int index) const {
auto it = entity_by_index_.find(index);
if (it == entity_by_index_.end()) return nullptr;
return &entities_[it->second];
}
// ── Helper: parse parameters ──
static std::vector<std::string> params(const IgesEntity& ent) {
return IgesParser::split_params(ent.parameters);
}
static double get_real(const std::vector<std::string>& p, size_t idx) {
if (idx < p.size()) return IgesParser::parse_real(p[idx]);
return 0.0;
}
static int get_int(const std::vector<std::string>& p, size_t idx) {
if (idx < p.size()) return IgesParser::parse_int(p[idx]);
return 0;
}
static Point3D get_point(const std::vector<std::string>& p, size_t idx) {
if (idx + 2 < p.size())
return Point3D(
IgesParser::parse_real(p[idx]),
IgesParser::parse_real(p[idx+1]),
IgesParser::parse_real(p[idx+2]));
return Point3D(0, 0, 0);
}
// ── Manifold Solid B-Rep (type 186) ──
BrepModel build_manifold_solid(int de_index) {
BrepModel model;
const auto* ent = get_entity(de_index);
if (!ent) return model;
auto p = params(*ent);
// 186 params: SHELL DE pointer (1 value)
if (p.empty()) return model;
int shell_de = IgesParser::parse_int(p[0]);
// Build shell
auto face_indices = convert_shell(shell_de, model);
if (!face_indices.empty()) {
int shell_id = model.add_shell(face_indices, true);
model.add_body({shell_id}, "IGES_Solid");
}
return model;
}
// ── Shell (type 514) ──
std::vector<int> convert_shell(int de_index, BrepModel& model) {
const auto* ent = get_entity(de_index);
if (!ent) return {};
auto p = params(*ent);
// 514 params: N, face_DE_1, face_DE_2, ..., face_DE_N
if (p.empty()) return {};
int n = IgesParser::parse_int(p[0]);
std::vector<int> face_ids;
for (int i = 0; i < n && static_cast<size_t>(i + 1) < p.size(); ++i) {
int face_de = IgesParser::parse_int(p[i + 1]);
int fid = convert_face(face_de, model);
if (fid >= 0) face_ids.push_back(fid);
}
return face_ids;
}
// ── Face (type 510) ──
int convert_face(int de_index, BrepModel& model) {
const auto* ent = get_entity(de_index);
if (!ent) return -1;
auto p = params(*ent);
// 510 params: surface_DE, N_loops, loop_flag_1, loop_DE_1, loop_flag_2, loop_DE_2, ...
if (p.size() < 2) return -1;
int surf_de = IgesParser::parse_int(p[0]);
int surf_id = convert_or_get_surface(surf_de, model);
int n_loops = IgesParser::parse_int(p[1]);
std::vector<int> loop_ids;
for (int i = 0; i < n_loops; ++i) {
size_t idx = 2 + static_cast<size_t>(i) * 2;
if (idx + 1 >= p.size()) break;
int loop_flag = IgesParser::parse_int(p[idx]); // 0=outer, 1=inner
int loop_de = IgesParser::parse_int(p[idx+1]);
int lid = convert_loop(loop_de, model, (loop_flag == 0));
if (lid >= 0) loop_ids.push_back(lid);
}
if (loop_ids.empty()) return -1;
return model.add_face(surf_id, loop_ids);
}
// ── Loop (type 508) ──
int convert_loop(int de_index, BrepModel& model, bool is_outer) {
const auto* ent = get_entity(de_index);
if (!ent) return -1;
auto p = params(*ent);
// 508 params: N, type_1, edge_DE_1, vertex_count_1, start_vertex_DE_1, end_vertex_DE_1,
// cur_srf_DE_1, start_param_1, end_param_1, ...
if (p.empty()) return -1;
int n = IgesParser::parse_int(p[0]);
std::vector<int> edge_indices;
int offset = 1; // skip N
for (int i = 0; i < n; ++i) {
if (offset + 2 >= static_cast<int>(p.size())) break;
int etype = IgesParser::parse_int(p[offset]);
int edge_de = IgesParser::parse_int(p[offset + 1]);
// Parse edge-only entries (TYPE=0): TYPE, EDGE_DE, COUNT, VX1..VX_COUNT, CS_DE, SP, EP
// Total fields per entry: 3 + COUNT + 3 = 6 + COUNT
int count = (offset + 2 < static_cast<int>(p.size())) ? IgesParser::parse_int(p[offset + 2]) : 2;
int skip = 6 + count; // TYPE + EDGE_DE + COUNT + COUNT_VX + CS_DE + SP + EP
int ei = convert_edge(edge_de, model);
if (ei >= 0) edge_indices.push_back(ei);
offset += skip;
if (offset > static_cast<int>(p.size())) break;
}
if (edge_indices.empty()) return -1;
return model.add_loop(edge_indices, is_outer);
}
// ── Edge (type 504) ──
int convert_edge(int de_index, BrepModel& model) {
const auto* ent = get_entity(de_index);
if (!ent) return -1;
auto p = params(*ent);
// 504 params: CURType, CURDEF, SVDE, SVINDEX, EVDE, EVINDEX, CSDE, SP, EP
// curve_type: 0=unspecified, 1=line, 2=arc, 3=conic, etc.
if (p.size() < 5) return -1;
int curve_de = IgesParser::parse_int(p[1]);
int v_start_de = IgesParser::parse_int(p[2]);
int v_end_de = IgesParser::parse_int(p[4]);
int vs = convert_vertex(v_start_de, model);
int ve = convert_vertex(v_end_de, model);
// Get the curve geometry
curves::NurbsCurve nc = get_or_build_curve(curve_de);
return model.add_edge(vs, ve, nc);
}
// ── Vertex (type 502) ──
int convert_vertex(int de_index, BrepModel& model) {
auto it = vp_cache_.find(de_index);
if (it != vp_cache_.end()) return it->second;
const auto* ent = get_entity(de_index);
if (!ent) return -1;
auto p = params(*ent);
// 502 params: X, Y, Z
Point3D pt = get_point(p, 0);
int vi = model.add_vertex(pt);
vp_cache_[de_index] = vi;
return vi;
}
// ── Curves ──
curves::NurbsCurve get_or_build_curve(int de_index) {
auto it = curve_cache_.find(de_index);
if (it != curve_cache_.end()) return it->second;
const auto* ent = get_entity(de_index);
if (!ent) {
// Return default line
auto nc = curves::NurbsCurve({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
curve_cache_[de_index] = nc;
return nc;
}
curves::NurbsCurve nc({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
try {
switch (ent->type_number) {
case 100: nc = convert_arc(*ent); break;
case 102: nc = convert_composite_curve(*ent); break;
case 104: nc = convert_conic_arc(*ent); break;
case 106: nc = convert_copious_data(*ent); break;
case 110: nc = convert_line(*ent); break;
case 126: nc = convert_bspline_curve(*ent); break;
default: break;
}
} catch (const std::exception&) {
// Keep default line on error
}
curve_cache_[de_index] = nc;
return nc;
}
// Type 100: Circular Arc
curves::NurbsCurve convert_arc(const IgesEntity& ent) {
auto p = params(ent);
// 100 params: ZT, X1, Y1, X2, Y2, X3, Y3
// Center = (X1, Y1), start_pt = (X2, Y2), end_pt = (X3, Y3)
// ZT is the plane depth (Z offset from definition plane)
if (p.size() < 7) throw std::runtime_error("Arc requires 7 parameters");
double zt = get_real(p, 0);
Point3D center(get_real(p, 1), get_real(p, 2), zt);
Point3D start_pt(get_real(p, 3), get_real(p, 4), zt);
Point3D end_pt(get_real(p, 5), get_real(p, 6), zt);
Vector3D vs = start_pt - center;
Vector3D ve = end_pt - center;
double R = vs.norm();
if (R < 1e-12) throw std::runtime_error("Zero radius arc");
Vector3D x_axis = vs.normalized();
// Compute the arc angle from start to end (counterclockwise)
double dot = vs.dot(ve) / (R * R);
dot = std::clamp(dot, -1.0, 1.0);
double angle = std::acos(dot);
// Determine sign: cross product Z component
double cross_z = vs.x() * ve.y() - vs.y() * ve.x();
if (cross_z < 0) angle = 2.0 * M_PI - angle;
if (angle < 1e-8) angle = 2.0 * M_PI; // full circle
// For arcs ≤ 180°, use 3 control points (degree 2)
// For larger arcs, split into multiple segments
// Simple approach: split into segments ≤ 180°
return make_arc_nurbs(center, x_axis, R, angle);
}
// Type 102: Composite Curve
curves::NurbsCurve convert_composite_curve(const IgesEntity& ent) {
auto p = params(ent);
// 102 params: N, entity_DE_1, entity_DE_2, ..., entity_DE_N
if (p.empty()) throw std::runtime_error("Empty composite curve");
int n = get_int(p, 0);
if (n <= 0) throw std::runtime_error("Composite curve has 0 entities");
// Return the first sub-curve as a fallback — full composite curve stitching
// would be complex; for now we return the first curve
int first_de = get_int(p, 1);
return get_or_build_curve(first_de);
}
// Type 104: Conic Arc
curves::NurbsCurve convert_conic_arc(const IgesEntity& ent) {
auto p = params(ent);
// 104 params: A, B, C, D, E, F, ZT, X1, Y1, X2, Y2
// General conic: Ax² + Bxy + Cy² + Dx + Ey + F = 0
// ZT: plane offset, (X1,Y1) start, (X2,Y2) end
// Form number determines type: 0=unspecified, 1=ellipse, 2=hyperbola, 3=parabola
if (ent.form_number == 1) {
// Ellipse
if (p.size() < 11) throw std::runtime_error("Conic ellipse requires 11 parameters");
double a = get_real(p, 0), b = get_real(p, 1), c = get_real(p, 2);
double d = get_real(p, 3), e_val = get_real(p, 4), f = get_real(p, 5);
double zt = get_real(p, 6);
// For a standard ellipse centered at origin with axes aligned:
// Center: (h, k) from completing the square
// Simple case: A=C and B=0 → circle, else approximate
// Extract center and semi-axes from the general conic
double denom = b*b - 4*a*c;
if (std::abs(denom) < 1e-12) throw std::runtime_error("Degenerate conic");
double h = (2*c*d - b*e_val) / denom;
double k = (2*a*e_val - b*d) / denom;
// Semi-axes computation simplified
double rx = std::sqrt(std::abs((a + c + std::sqrt((a-c)*(a-c) + b*b)) / (-2 * (a*h*h + b*h*k + c*k*k - f))));
double ry = std::sqrt(std::abs((a + c - std::sqrt((a-c)*(a-c) + b*b)) / (-2 * (a*h*h + b*h*k + c*k*k - f))));
(void)rx; (void)ry;
(void)h; (void)k;
(void)zt;
// Use the start/end points and construct a NURBS ellipse
Point3D start(get_real(p, 8), get_real(p, 9), zt);
// For now return an approximation as a circular arc
Point3D center(0, 0, zt);
double R = (start - center).norm();
return make_arc_nurbs(center, Vector3D(1, 0, 0), R, 2.0 * M_PI);
}
// Fallback: treat as arc
if (p.size() >= 11) {
double zt = get_real(p, 6);
Point3D start(get_real(p, 7), get_real(p, 8), zt);
Point3D end(get_real(p, 9), get_real(p, 10), zt);
Point3D center((start.x()+end.x())/2, (start.y()+end.y())/2, zt);
double R = (start - center).norm();
return make_arc_nurbs(center, Vector3D(1, 0, 0), R, M_PI);
}
throw std::runtime_error("Insufficient conic parameters");
}
// Type 106: Copious Data
curves::NurbsCurve convert_copious_data(const IgesEntity& ent) {
auto p = params(ent);
// 106 params: IP, N, X1, Y1, Z1, X2, Y2, Z2, ...
// IP: data interpretation (1=points, 2=form 1, 3=form n)
// For polyline (form 0, IP=1): N points
if (p.size() < 3) throw std::runtime_error("Copious data has too few parameters");
int ip = get_int(p, 0);
if (ip == 1) {
// Simple polyline
int n = get_int(p, 1);
size_t idx = 2;
std::vector<Point3D> pts;
for (int i = 0; i < n && idx + 2 < p.size(); ++i) {
pts.push_back(Point3D(get_real(p, idx), get_real(p, idx+1), get_real(p, idx+2)));
idx += 3;
}
if (pts.size() < 2) throw std::runtime_error("Polyline needs at least 2 points");
// Build a degree-1 NURBS polyline
std::vector<double> knots;
std::vector<double> wg(pts.size(), 1.0);
for (int i = 0; i < static_cast<int>(pts.size()) + 2; ++i)
knots.push_back(static_cast<double>(std::min(i, static_cast<int>(pts.size()) - 1)));
return curves::NurbsCurve(pts, knots, wg, 1);
}
throw std::runtime_error("Unsupported copious data IP type");
}
// Type 110: Line
curves::NurbsCurve convert_line(const IgesEntity& ent) {
auto p = params(ent);
// 110 params: X1, Y1, Z1, X2, Y2, Z2
if (p.size() < 6) throw std::runtime_error("Line requires 6 parameters");
Point3D p1(get_real(p, 0), get_real(p, 1), get_real(p, 2));
Point3D p2(get_real(p, 3), get_real(p, 4), get_real(p, 5));
return curves::NurbsCurve({p1, p2}, {0, 0, 1, 1}, {1, 1}, 1);
}
// Type 126: Rational B-Spline Curve
curves::NurbsCurve convert_bspline_curve(const IgesEntity& ent) {
auto p = params(ent);
// 126 params: K, M, PROP1, PROP2, PROP3, N, A(N+1), knot sequence, weights, control points
// K: upper index of sum (N = number of control points - 1, K = N+M where M=order)
// M: degree (= order - 1)
// PROP1: planar flag (0=open, 1=closed)
// PROP2: curve type (0=polynomial, 1=rational)
// PROP3: periodic flag (0=non-periodic, 1=periodic)
// N: number of control points - 1
// A: knot sequence (N+M+1 values)
// W: weights (K-N+1 values, if PROP2=1)
// Control points: X0,Y0,Z0, X1,Y1,Z1, ...
if (p.size() < 6) throw std::runtime_error("B-Spline curve requires at least 6 parameters");
int K = get_int(p, 0); // upper index
int M = get_int(p, 1); // degree (order M = degree + 1... wait, M in IGES is the basis function order?)
// Actually: M = order = degree + 1. So degree = M - 1
// Re-reading IGES spec: K is upper index (N+M-1 traditionally in IGES 126),
// M is the degree of the basis functions.
// N+1 is the number of control points (N = K - M)
int prop1 = get_int(p, 2);
int prop2 = get_int(p, 3);
int prop3 = get_int(p, 4);
int N_val = get_int(p, 5); // N+1 control points
int degree = M;
int num_cps = N_val + 1;
int num_knots = K + 1; // A(N+1) means N+1 values stored, but IGES here means K+1 values
// Parse knot sequence
size_t idx = 6;
std::vector<double> knots;
for (int i = 0; i <= K && idx < p.size(); ++i) {
knots.push_back(get_real(p, idx++));
}
// Parse weights if rational
std::vector<double> weights;
if (prop2 == 1) {
// weights: K - N + 1 values (= K - N_val + 1)
int num_weights = K - N_val + 1;
for (int i = 0; i < num_weights && idx < p.size(); ++i) {
weights.push_back(get_real(p, idx++));
}
} else {
weights.assign(num_cps, 1.0);
}
// Parse control points
std::vector<Point3D> cps;
for (int i = 0; i < num_cps && idx + 2 < p.size(); ++i) {
cps.push_back(Point3D(get_real(p, idx), get_real(p, idx+1), get_real(p, idx+2)));
idx += 3;
}
if (cps.empty()) throw std::runtime_error("No control points in B-Spline");
// Normalize knot vector for open-uniform if needed
if (knots.size() < static_cast<size_t>(cps.size() + degree + 1)) {
// Generate open-uniform knots
knots.clear();
for (int i = 0; i < static_cast<int>(cps.size()) + degree + 1; ++i) {
knots.push_back(static_cast<double>(std::clamp(i - degree, 0,
static_cast<int>(cps.size()) - degree)));
}
}
if (weights.empty())
weights.assign(cps.size(), 1.0);
return curves::NurbsCurve(cps, knots, weights, degree);
}
// ── Surfaces ──
int convert_or_get_surface(int de_index, BrepModel& model) {
auto it = surf_cache_.find(de_index);
if (it != surf_cache_.end()) return it->second;
curves::NurbsSurface surf = get_or_build_surface(de_index);
int sid = model.add_surface(surf);
surf_cache_[de_index] = sid;
return sid;
}
curves::NurbsSurface get_or_build_surface(int de_index) {
auto it = surface_cache_.find(de_index);
if (it != surface_cache_.end()) return it->second;
const auto* ent = get_entity(de_index);
if (!ent) {
std::vector<std::vector<Point3D>> g = {{Point3D(0,0,0), Point3D(1,0,0)},
{Point3D(0,1,0), Point3D(1,1,0)}};
auto ns = curves::NurbsSurface(g, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
surface_cache_[de_index] = ns;
return ns;
}
std::vector<std::vector<Point3D>> g0 = {{Point3D(0,0,0), Point3D(1,0,0)},
{Point3D(0,1,0), Point3D(1,1,0)}};
curves::NurbsSurface ns(g0, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
try {
switch (ent->type_number) {
case 108: ns = convert_plane(*ent); break;
case 114: ns = convert_parametric_spline_surface(*ent); break;
case 118: ns = convert_ruled_surface(*ent); break;
case 120: ns = convert_revolution_surface(*ent); break;
case 122: ns = convert_tabulated_cylinder(*ent); break;
case 128: ns = convert_bspline_surface(*ent); break;
case 140: ns = convert_offset_surface(*ent); break;
case 192: ns = convert_cylinder_surface(*ent); break;
case 194: ns = convert_cone_surface(*ent); break;
case 196: ns = convert_sphere_surface(*ent); break;
case 198: ns = convert_torus_surface(*ent); break;
default: break;
}
} catch (const std::exception&) {
// Keep default surface
}
surface_cache_[de_index] = ns;
return ns;
}
// Type 108: Plane
curves::NurbsSurface convert_plane(const IgesEntity& ent) {
auto p = params(ent);
// 108 params: A, B, C, D (plane equation: Ax + By + Cz + D = 0)
// Plus an optional pointer to a closed boundary curve
if (p.size() < 4) throw std::runtime_error("Plane requires 4 parameters");
double a = get_real(p, 0), b = get_real(p, 1);
double c = get_real(p, 2), d = get_real(p, 3);
Vector3D normal(a, b, c);
double len = normal.norm();
if (len < 1e-12) throw std::runtime_error("Zero-length plane normal");
normal = normal / len;
d = d / len;
// Point on plane: project origin
double t = -d; // distance from origin along normal
Point3D origin = t * normal;
// Build two tangent directions
Vector3D u = std::abs(normal.x()) < 0.9 ? Vector3D(1, 0, 0).cross(normal).normalized()
: Vector3D(0, 1, 0).cross(normal).normalized();
Vector3D v = normal.cross(u).normalized();
double s = 1e6;
std::vector<std::vector<Point3D>> grid = {
{origin - s*u - s*v, origin - s*u + s*v},
{origin + s*u - s*v, origin + s*u + s*v}
};
return curves::NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
// Type 114: Parametric Spline Surface
curves::NurbsSurface convert_parametric_spline_surface(const IgesEntity& ent) {
(void)ent;
// Simplified: return a default plane
throw std::runtime_error("Parametric spline surface not fully implemented");
}
// Type 118: Ruled Surface
curves::NurbsSurface convert_ruled_surface(const IgesEntity& ent) {
auto p = params(ent);
// 118 params: curve_DE_1, curve_DE_2, direction_flag, developable_flag
if (p.size() < 2) throw std::runtime_error("Ruled surface requires at least 2 parameters");
int c1_de = get_int(p, 0);
int c2_de = get_int(p, 1);
auto c1 = get_or_build_curve(c1_de);
auto c2 = get_or_build_curve(c2_de);
return curves::NurbsSurface::ruled(c1, c2);
}
// Type 120: Surface of Revolution
curves::NurbsSurface convert_revolution_surface(const IgesEntity& ent) {
auto p = params(ent);
// 120 params: curve_DE, axis_origin_X, axis_origin_Y, axis_origin_Z,
// axis_dir_X, axis_dir_Y, axis_dir_Z, start_angle, end_angle
if (p.size() < 8) throw std::runtime_error("Surface of revolution requires at least 8 parameters");
int curve_de = get_int(p, 0);
Point3D axis_origin(get_real(p, 1), get_real(p, 2), get_real(p, 3));
Vector3D axis_dir(get_real(p, 4), get_real(p, 5), get_real(p, 6));
double start_angle = get_real(p, 7);
double end_angle = (p.size() >= 9) ? get_real(p, 8) : 2.0 * M_PI;
auto profile = get_or_build_curve(curve_de);
double sweep = end_angle - start_angle;
return curves::NurbsSurface::revolve(profile, axis_origin, axis_dir, sweep);
}
// Type 122: Tabulated Cylinder
curves::NurbsSurface convert_tabulated_cylinder(const IgesEntity& ent) {
auto p = params(ent);
// 122 params: curve_DE, dir_X, dir_Y, dir_Z
if (p.size() < 4) throw std::runtime_error("Tabulated cylinder requires at least 4 parameters");
int curve_de = get_int(p, 0);
Vector3D dir(get_real(p, 1), get_real(p, 2), get_real(p, 3));
auto directrix = get_or_build_curve(curve_de);
// Build a ruled surface by offsetting the curve along dir
const auto& cps = directrix.control_points();
const auto& wg = directrix.weights();
const auto& kts = directrix.knots();
int deg = directrix.degree();
std::vector<Point3D> cps2;
for (const auto& cp : cps)
cps2.push_back(cp + dir);
auto c2 = curves::NurbsCurve(cps2, kts, wg, deg);
return curves::NurbsSurface::ruled(directrix, c2);
}
// Type 128: Rational B-Spline Surface
curves::NurbsSurface convert_bspline_surface(const IgesEntity& ent) {
auto p = params(ent);
// 128 params:
// K1, K2, M1, M2, PROP1, PROP2, PROP3, PROP4, PROP5,
// N1, N2,
// S(N1+1): u-knot sequence (N1+M1+1 values = K1+1 values)
// T(N2+1): v-knot sequence (N2+M2+1 values = K2+1 values)
// W: weights (if PROP5=1) → ((K1-N1)+1) * ((K2-N2)+1) values row-major
// Control points: (N1+1)*(N2+1) points, row-major in v then u? or u then v?
// Actually: X11,Y11,Z11, X12,Y12,Z12, ... (varying first index fastest)
if (p.size() < 11) throw std::runtime_error("B-Spline surface requires at least 11 parameters");
int K1 = get_int(p, 0);
int K2 = get_int(p, 1);
int M1 = get_int(p, 2);
int M2 = get_int(p, 3);
// PROP1-PROP4: planar/closed u/closed v/polynomial flags (skip)
int prop5 = get_int(p, 8); // rational flag
int N1 = get_int(p, 9);
int N2 = get_int(p, 10);
int deg_u = M1;
int deg_v = M2;
int nu = N1 + 1;
int nv = N2 + 1;
size_t idx = 11;
// U knot sequence: K1+1 values
std::vector<double> ku;
for (int i = 0; i <= K1 && idx < p.size(); ++i)
ku.push_back(get_real(p, idx++));
// V knot sequence: K2+1 values
std::vector<double> kv;
for (int i = 0; i <= K2 && idx < p.size(); ++i)
kv.push_back(get_real(p, idx++));
// Weights (if rational)
std::vector<std::vector<double>> weights;
if (prop5 == 1) {
int nwu = K1 - N1 + 1;
int nwv = K2 - N2 + 1;
weights.resize(nwu);
for (int i = 0; i < nwu; ++i) {
weights[i].resize(nwv);
for (int j = 0; j < nwv && idx < p.size(); ++j) {
weights[i][j] = get_real(p, idx++);
}
}
}
// Control points: nu × nv, row-major
std::vector<std::vector<Point3D>> grid(nu);
for (int i = 0; i < nu; ++i) {
grid[i].resize(nv);
for (int j = 0; j < nv && idx + 2 < p.size(); ++j) {
grid[i][j] = Point3D(get_real(p, idx), get_real(p, idx+1), get_real(p, idx+2));
idx += 3;
}
}
if (grid.empty() || grid[0].empty()) throw std::runtime_error("Empty B-Spline surface grid");
// Normalize knots if needed
if (ku.size() < static_cast<size_t>(nu + deg_u + 1)) {
ku.clear();
for (int i = 0; i < nu + deg_u + 1; ++i)
ku.push_back(static_cast<double>(std::clamp(i - deg_u, 0, nu - deg_u)));
}
if (kv.size() < static_cast<size_t>(nv + deg_v + 1)) {
kv.clear();
for (int i = 0; i < nv + deg_v + 1; ++i)
kv.push_back(static_cast<double>(std::clamp(i - deg_v, 0, nv - deg_v)));
}
return curves::NurbsSurface(grid, ku, kv, weights, deg_u, deg_v);
}
// Type 140: Offset Surface
curves::NurbsSurface convert_offset_surface(const IgesEntity& ent) {
auto p = params(ent);
// 140 params: base_surface_DE, offset_distance
if (p.size() < 2) throw std::runtime_error("Offset surface requires at least 2 parameters");
int base_de = get_int(p, 0);
double offset = get_real(p, 1);
// Simple offset: return the base surface with a note — full offset
// implementation would require offsetting the NURBS, which is complex.
// For now return the base surface (geometric offset approximation)
(void)offset;
return get_or_build_surface(base_de);
}
// Type 192: Right Circular Cylindrical Surface
curves::NurbsSurface convert_cylinder_surface(const IgesEntity& ent) {
auto p = params(ent);
// 192 params: location_X, location_Y, location_Z, axis_X, axis_Y, axis_Z, radius
if (p.size() < 7) throw std::runtime_error("Cylinder requires 7 parameters");
Point3D loc(get_real(p, 0), get_real(p, 1), get_real(p, 2));
Vector3D axis(get_real(p, 3), get_real(p, 4), get_real(p, 5));
double R = get_real(p, 6);
return make_cylinder_surface(loc, axis, R);
}
// Type 194: Right Circular Conical Surface
curves::NurbsSurface convert_cone_surface(const IgesEntity& ent) {
auto p = params(ent);
// 194 params: location_X, location_Y, location_Z, axis_X, axis_Y, axis_Z, radius, semi_angle
if (p.size() < 8) throw std::runtime_error("Cone requires 8 parameters");
Point3D loc(get_real(p, 0), get_real(p, 1), get_real(p, 2));
Vector3D axis(get_real(p, 3), get_real(p, 4), get_real(p, 5));
double R = get_real(p, 6);
double angle = get_real(p, 7);
return make_cone_surface(loc, axis, R, angle);
}
// Type 196: Spherical Surface
curves::NurbsSurface convert_sphere_surface(const IgesEntity& ent) {
auto p = params(ent);
// 196 params: center_X, center_Y, center_Z, radius
if (p.size() < 4) throw std::runtime_error("Sphere requires 4 parameters");
Point3D center(get_real(p, 0), get_real(p, 1), get_real(p, 2));
double R = get_real(p, 3);
return make_sphere_surface(center, R);
}
// Type 198: Toroidal Surface
curves::NurbsSurface convert_torus_surface(const IgesEntity& ent) {
auto p = params(ent);
// 198 params: center_X, center_Y, center_Z, axis_X, axis_Y, axis_Z,
// major_radius, minor_radius
if (p.size() < 8) throw std::runtime_error("Torus requires 8 parameters");
Point3D center(get_real(p, 0), get_real(p, 1), get_real(p, 2));
Vector3D axis(get_real(p, 3), get_real(p, 4), get_real(p, 5));
double R = get_real(p, 6);
double r = get_real(p, 7);
return make_torus_surface(center, axis, R, r);
}
// ── Geometry factory helpers ──
static curves::NurbsCurve make_arc_nurbs(const Point3D& C, const Vector3D& X,
double R, double angle) {
// Build a circular arc from angle 0 to `angle` in the XY plane defined by X and Y
// (Y = Z × X, where Z is (0,0,1))
// Returns degree-2 rational NURBS
Vector3D Y(0, 0, 1);
if (std::abs(X.z()) > 0.99) Y = Vector3D(0, 1, 0);
Y = Y.cross(X).normalized();
// For full circle and large arcs, use multiples of 90° segments
int num_segments = std::max(1, static_cast<int>(std::ceil(angle / (M_PI_2))));
double seg_angle = angle / num_segments;
std::vector<Point3D> cps_all;
std::vector<double> wgs_all;
int n_seg_cps = 3; // each 90° segment: 3 control points
int total_segments = num_segments;
for (int s = 0; s < total_segments; ++s) {
double a0 = s * seg_angle;
double a2 = (s + 1) * seg_angle;
double a1 = (a0 + a2) / 2.0;
double w = std::cos(seg_angle / 2.0); // = cos(half segment angle)
Point3D p0 = C + R * (std::cos(a0) * X + std::sin(a0) * Y);
Point3D p1 = C + (R / w) * (std::cos(a1) * X + std::sin(a1) * Y);
Point3D p2 = C + R * (std::cos(a2) * X + std::sin(a2) * Y);
if (s == 0) {
cps_all.push_back(p0);
wgs_all.push_back(1.0);
}
cps_all.push_back(p1);
wgs_all.push_back(w);
cps_all.push_back(p2);
wgs_all.push_back(1.0);
}
// Build knots for degree-2 rational curve
int n_cps = static_cast<int>(cps_all.size());
std::vector<double> knots;
int degree = 2;
// Open-uniform: degree duplicate at each end
int num_segs = (n_cps - 1) / 2;
for (int i = 0; i < n_cps + degree + 1; ++i) {
int k = i - degree;
if (k < 0) k = 0;
if (k > num_segs) k = num_segs;
knots.push_back(static_cast<double>(k));
}
return curves::NurbsCurve(cps_all, knots, wgs_all, degree);
}
static curves::NurbsSurface make_cylinder_surface(const Point3D& C, const Vector3D& axis,
double R) {
// Build two orthogonal directions perpendicular to axis
Vector3D u = std::abs(axis.x()) < 0.9 ? Vector3D(1, 0, 0).cross(axis).normalized()
: Vector3D(0, 1, 0).cross(axis).normalized();
Vector3D v = axis.cross(u).normalized();
double h = 1e6;
double w = std::sqrt(2.0) / 2.0;
// Circle profile in uv plane (9 CPs for 360°)
std::vector<Point3D> circ;
for (int i = 0; i < 9; ++i) {
double theta = i * M_PI_4;
double cp = std::cos(theta), sp = std::sin(theta);
circ.push_back(C + R * (cp * u + sp * v));
}
std::vector<std::vector<Point3D>> grid = {circ};
for (const auto& pt : circ)
grid.push_back({pt + h * axis});
// Actually we need 2 rows
grid.clear();
grid.push_back(circ);
std::vector<Point3D> top;
for (const auto& pt : circ)
top.push_back(pt + h * axis);
grid.push_back(top);
std::vector<double> ku;
for (int i = 0; i < 12; ++i) ku.push_back(i / 4.0);
std::vector<double> kv = {0, 0, 1, 1};
std::vector<double> cw = {1, w, 1, w, 1, w, 1, w, 1};
return curves::NurbsSurface(grid, ku, kv, {cw, cw}, 2, 1);
}
static curves::NurbsSurface make_cone_surface(const Point3D& C, const Vector3D& axis,
double R, double angle) {
Vector3D u = std::abs(axis.x()) < 0.9 ? Vector3D(1, 0, 0).cross(axis).normalized()
: Vector3D(0, 1, 0).cross(axis).normalized();
Vector3D v = axis.cross(u).normalized();
double h = 1e6;
double R_top = R + h * std::tan(angle);
double w = std::sqrt(2.0) / 2.0;
std::vector<Point3D> bottom, top_circ;
for (int i = 0; i < 9; ++i) {
double theta = i * M_PI_4;
double cp = std::cos(theta), sp = std::sin(theta);
bottom.push_back(C + R * (cp * u + sp * v));
top_circ.push_back(C + h * axis + R_top * (cp * u + sp * v));
}
std::vector<double> ku;
for (int i = 0; i < 12; ++i) ku.push_back(i / 4.0);
std::vector<double> kv = {0, 0, 1, 1};
std::vector<double> cw = {1, w, 1, w, 1, w, 1, w, 1};
return curves::NurbsSurface({bottom, top_circ}, ku, kv, {cw, cw}, 2, 1);
}
static curves::NurbsSurface make_sphere_surface(const Point3D& C, double R) {
// Sphere as surface of revolution of a half-circle
double w = std::sqrt(2.0) / 2.0;
// Revolve half-circle profile about Z axis
// 3 rows (v direction: top pole, equator, bottom pole) × 9 cols (u direction: full circle)
// The profile: half circle from +Z to -Z in XZ plane:
// P0=(0,0,R), P1=(R,0,0), P2=(0,0,-R) with weights [1, w, 1] → degree 2
std::vector<std::vector<Point3D>> grid(3);
std::vector<double> cw = {1, w, 1, w, 1, w, 1, w, 1};
for (int i = 0; i < 9; ++i) {
double theta = i * M_PI_4;
double ct = std::cos(theta), st = std::sin(theta);
// Top pole (all collapsed to one point)
grid[0].push_back(C + Point3D(0, 0, R));
// Equator ring
grid[1].push_back(C + Point3D(R * ct, R * st, 0));
// Bottom pole
grid[2].push_back(C + Point3D(0, 0, -R));
}
std::vector<double> ku;
for (int i = 0; i < 12; ++i) ku.push_back(i / 4.0);
std::vector<double> kv = {0, 0, 0, 1, 1, 1};
std::vector<std::vector<double>> weights = {
{1, w, 1, w, 1, w, 1, w, 1},
{w, w*w, w, w*w, w, w*w, w, w*w, w},
{1, w, 1, w, 1, w, 1, w, 1},
};
return curves::NurbsSurface(grid, ku, kv, weights, 2, 2);
}
static curves::NurbsSurface make_torus_surface(const Point3D& C, const Vector3D& axis,
double R, double r) {
// Torus: circle of radius r swept around circle of radius R
Vector3D u = std::abs(axis.x()) < 0.9 ? Vector3D(1, 0, 0).cross(axis).normalized()
: Vector3D(0, 1, 0).cross(axis).normalized();
Vector3D v = axis.cross(u).normalized();
double w = std::sqrt(2.0) / 2.0;
std::vector<std::vector<Point3D>> grid(9);
std::vector<std::vector<double>> wg(9);
for (int i = 0; i < 9; ++i) {
double theta = i * M_PI_4;
double ct = std::cos(theta), st = std::sin(theta);
// Major circle center
Point3D mc = C + R * (ct * u + st * v);
// Minor circle in plane (radial, axis)
Vector3D rad = ct * u + st * v;
for (int j = 0; j < 9; ++j) {
double phi = j * M_PI_4;
double cp = std::cos(phi), sp = std::sin(phi);
grid[i].push_back(mc + r * (cp * rad + sp * axis));
}
// Weights
std::vector<double> row_w;
for (int j = 0; j < 9; ++j) {
double wu = (i % 2 == 0) ? 1.0 : w;
double wv_val = (j % 2 == 0) ? 1.0 : w;
row_w.push_back(wu * wv_val);
}
wg[i] = row_w;
}
std::vector<double> ku, kv;
for (int i = 0; i < 12; ++i) { ku.push_back(i / 4.0); kv.push_back(i / 4.0); }
return curves::NurbsSurface(grid, ku, kv, wg, 2, 2);
}
std::unordered_map<int, curves::NurbsSurface> surface_cache_;
};
// ─────────────────────────────────────────────────────────────
// Public API
// ─────────────────────────────────────────────────────────────
std::vector<BrepModel> import_iges_from_string(const std::string& data) {
clear_error();
if (data.empty()) {
set_error(IgesError::ParseError, "Empty IGES data");
return {};
}
IgesParser parser(data);
if (!parser.parse())
return {};
IgesToBrep converter(parser.entities());
try {
return converter.convert();
} catch (const std::exception& e) {
set_error(IgesError::ParseError, std::string("Conversion error: ") + e.what());
return {};
}
}
std::vector<BrepModel> import_iges(const std::string& filepath) {
clear_error();
std::ifstream file(filepath);
if (!file.is_open()) {
set_error(IgesError::FileNotFound, "Cannot open file: " + filepath);
return {};
}
std::ostringstream buf;
buf << file.rdbuf();
return import_iges_from_string(buf.str());
}
} // namespace vde::brep