Files
ViewDesignEngine/src/brep/step_import.cpp
T
茂之钳 4a07bf34dc
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 30s
fix: remove duplicate edge declaration in modeling.cpp
2026-07-24 08:45:36 +00:00

1461 lines
56 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "vde/brep/step_import.h"
#include "vde/curves/nurbs_curve.h"
#include "vde/curves/nurbs_surface.h"
#include <fstream>
#include <sstream>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <stdexcept>
namespace vde::brep {
using core::Point3D;
using core::Vector3D;
// ─────────────────────────────────────────────────────────────
// Error state (thread-safe enough for single-threaded use)
// ─────────────────────────────────────────────────────────────
static StepError g_last_error = StepError::Ok;
static std::string g_last_error_msg;
StepError step_last_error() { return g_last_error; }
const std::string& step_last_error_message() { return g_last_error_msg; }
static void set_error(StepError e, const std::string& msg) {
g_last_error = e;
g_last_error_msg = msg;
}
static void clear_error() { g_last_error = StepError::Ok; g_last_error_msg.clear(); }
// ─────────────────────────────────────────────────────────────
// Tokenizer
// ─────────────────────────────────────────────────────────────
enum class TokenKind : uint8_t {
Eof, Id, Keyword, String, Number, Integer,
LParen, RParen, Comma, Semicolon, Equals,
Star, Enumeration, Omitted,
};
struct Token {
TokenKind kind = TokenKind::Eof;
std::string text; // raw text
int entity_id = 0; // valid for Id tokens
};
class StepTokenizer {
public:
explicit StepTokenizer(std::string data) : data_(std::move(data)) {}
Token peek() {
if (!peeked_)
peeked_ = next_token();
return *peeked_;
}
Token consume() {
Token t = peek();
peeked_.reset();
return t;
}
private:
std::string data_;
size_t pos_ = 0;
std::optional<Token> peeked_;
void skip_ws() {
while (pos_ < data_.size() && (data_[pos_] == ' ' || data_[pos_] == '\t'
|| data_[pos_] == '\r' || data_[pos_] == '\n'))
++pos_;
// Skip comments: /* ... */
if (pos_ < data_.size() && data_[pos_] == '/'
&& pos_+1 < data_.size() && data_[pos_+1] == '*') {
pos_ += 2;
while (pos_+1 < data_.size() && !(data_[pos_]=='*' && data_[pos_+1]=='/'))
++pos_;
if (pos_+1 < data_.size()) pos_ += 2;
skip_ws();
}
}
Token next_token() {
skip_ws();
if (pos_ >= data_.size()) return Token{TokenKind::Eof};
char c = data_[pos_];
// Entity ID: # followed by digits
if (c == '#') {
size_t start = pos_ + 1;
++pos_;
while (pos_ < data_.size() && std::isdigit(static_cast<unsigned char>(data_[pos_])))
++pos_;
Token t{TokenKind::Id};
t.text = data_.substr(start, pos_ - start);
t.entity_id = std::stoi(t.text);
return t;
}
// Single-quoted string
if (c == '\'') {
++pos_; // skip opening quote
std::string val;
while (pos_ < data_.size()) {
if (data_[pos_] == '\'') {
if (pos_+1 < data_.size() && data_[pos_+1] == '\'') {
val += '\'';
pos_ += 2; // escaped single quote
} else {
++pos_; // closing quote
break;
}
} else {
val += data_[pos_];
++pos_;
}
}
Token t{TokenKind::String};
t.text = val;
return t;
}
// Operators / punctuation
switch (c) {
case '(': ++pos_; return Token{TokenKind::LParen, "("};
case ')': ++pos_; return Token{TokenKind::RParen, ")"};
case ',': ++pos_; return Token{TokenKind::Comma, ","};
case ';': ++pos_; return Token{TokenKind::Semicolon, ";"};
case '=': ++pos_; return Token{TokenKind::Equals, "="};
case '*': ++pos_; return Token{TokenKind::Star, "*"};
}
// Enumeration: .T. or .F. or .U. etc.
if (c == '.') {
size_t start = pos_;
++pos_;
while (pos_ < data_.size() && data_[pos_] != '.') ++pos_;
if (pos_ < data_.size()) ++pos_; // skip closing dot
Token t{TokenKind::Enumeration};
t.text = data_.substr(start, pos_ - start);
return t;
}
// Omitted: $
if (c == '$') { ++pos_; return Token{TokenKind::Omitted, "$"}; }
// Number or keyword
{
size_t start = pos_;
bool is_number = (c == '-' || c == '+' || c == '.' || std::isdigit(static_cast<unsigned char>(c)));
while (pos_ < data_.size()
&& data_[pos_] != ' ' && data_[pos_] != '\t' && data_[pos_] != '\r' && data_[pos_] != '\n'
&& data_[pos_] != '(' && data_[pos_] != ')' && data_[pos_] != ',' && data_[pos_] != ';'
&& data_[pos_] != '=' && data_[pos_] != '\'' && data_[pos_] != '$') {
++pos_;
}
std::string tok = data_.substr(start, pos_ - start);
if (is_number && !tok.empty()) {
// Check if it's a pure integer (no decimal point, no exponent)
bool is_int = true;
for (char ch : tok) {
if (ch == '.' || ch == 'e' || ch == 'E') { is_int = false; break; }
}
if (is_int && tok != "-" && tok != "+") {
try {
Token t{TokenKind::Integer};
t.text = tok;
t.entity_id = std::stoi(tok);
return t;
} catch (...) {}
}
Token t{TokenKind::Number};
t.text = tok;
return t;
}
Token t{TokenKind::Keyword};
t.text = tok;
return t;
}
}
};
// ─────────────────────────────────────────────────────────────
// STEP Value type for parsed arguments
// ─────────────────────────────────────────────────────────────
struct StepValue;
using StepList = std::vector<StepValue>;
struct StepValue {
enum class Type : uint8_t { Omitted, Integer, Float, String, Enum, Ref, List };
Type type = Type::Omitted;
int ival = 0;
double dval = 0.0;
std::string sval;
StepList list;
};
// ─────────────────────────────────────────────────────────────
// Entity record
// ─────────────────────────────────────────────────────────────
struct StepEntity {
std::string type;
StepList args;
};
// ─────────────────────────────────────────────────────────────
// Parsed STEP model (raw entity map)
// ─────────────────────────────────────────────────────────────
class StepParser {
public:
explicit StepParser(std::string data) : tokenizer_(std::move(data)) {}
bool parse_data_section() {
// Find DATA; section
while (true) {
Token t = tokenizer_.consume();
if (t.kind == TokenKind::Eof) {
set_error(StepError::ParseError, "DATA section not found");
return false;
}
if (t.kind == TokenKind::Keyword && t.text == "DATA") {
Token s = tokenizer_.consume();
if (s.kind == TokenKind::Semicolon) break;
}
}
// Parse entities until ENDSEC;
while (true) {
Token t = tokenizer_.peek();
if (t.kind == TokenKind::Keyword && t.text == "ENDSEC") {
tokenizer_.consume();
Token s = tokenizer_.consume();
if (s.kind == TokenKind::Semicolon) break;
set_error(StepError::ParseError, "Expected ; after ENDSEC");
return false;
}
if (t.kind == TokenKind::Eof) {
set_error(StepError::ParseError, "Unexpected EOF in DATA section");
return false;
}
if (!parse_entity()) return false;
}
return true;
}
const std::unordered_map<int, StepEntity>& entities() const { return entities_; }
private:
StepTokenizer tokenizer_;
std::unordered_map<int, StepEntity> entities_;
bool parse_entity() {
// #ID = TYPE ( args ) ;
Token id_tok = tokenizer_.consume();
if (id_tok.kind != TokenKind::Id) {
set_error(StepError::ParseError, "Expected entity ID, got: " + id_tok.text);
return false;
}
Token eq = tokenizer_.consume();
if (eq.kind != TokenKind::Equals) {
set_error(StepError::ParseError, "Expected = after entity ID");
return false;
}
Token type_tok = tokenizer_.consume();
if (type_tok.kind != TokenKind::Keyword) {
set_error(StepError::ParseError, "Expected entity type keyword");
return false;
}
Token lp = tokenizer_.consume();
if (lp.kind != TokenKind::LParen) {
set_error(StepError::ParseError, "Expected ( after entity type");
return false;
}
StepList args = parse_arg_list();
// parse_arg_list already consumes the closing )
Token semi = tokenizer_.consume();
if (semi.kind != TokenKind::Semicolon) {
set_error(StepError::ParseError, "Expected ; at end of entity");
return false;
}
entities_[id_tok.entity_id] = {type_tok.text, std::move(args)};
return true;
}
StepList parse_arg_list() {
StepList args;
while (true) {
Token t = tokenizer_.peek();
if (t.kind == TokenKind::RParen) {
tokenizer_.consume();
return args;
}
if (t.kind == TokenKind::Comma) {
tokenizer_.consume();
continue;
}
if (t.kind == TokenKind::LParen) {
tokenizer_.consume();
StepValue sv;
sv.type = StepValue::Type::List;
sv.list = parse_arg_list();
args.push_back(std::move(sv));
continue;
}
StepValue sv = parse_value();
args.push_back(std::move(sv));
}
}
StepValue parse_value() {
Token t = tokenizer_.consume();
StepValue sv;
switch (t.kind) {
case TokenKind::Omitted:
sv.type = StepValue::Type::Omitted;
break;
case TokenKind::Id:
sv.type = StepValue::Type::Ref;
sv.ival = t.entity_id;
break;
case TokenKind::Integer:
sv.type = StepValue::Type::Integer;
sv.ival = std::stoi(t.text);
sv.dval = sv.ival;
break;
case TokenKind::Number:
sv.type = StepValue::Type::Float;
sv.dval = std::stod(t.text);
break;
case TokenKind::String:
sv.type = StepValue::Type::String;
sv.sval = t.text;
break;
case TokenKind::Enumeration:
sv.type = StepValue::Type::Enum;
sv.sval = t.text;
break;
case TokenKind::Star:
// * used as a placeholder in some STEP files — treat as Omitted-deriv
sv.type = StepValue::Type::Omitted;
break;
default:
sv.type = StepValue::Type::Omitted;
break;
}
return sv;
}
};
// ─────────────────────────────────────────────────────────────
// Helper: extract values from parsed args
// ─────────────────────────────────────────────────────────────
static double to_double(const StepValue& v) {
switch (v.type) {
case StepValue::Type::Integer: return static_cast<double>(v.ival);
case StepValue::Type::Float: return v.dval;
default: return 0.0;
}
}
static std::string to_string(const StepValue& v) {
if (v.type == StepValue::Type::String) return v.sval;
return "";
}
static Point3D to_point(const StepList& lst) {
if (lst.size() >= 3)
return Point3D(to_double(lst[0]), to_double(lst[1]), to_double(lst[2]));
return Point3D(0, 0, 0);
}
static Vector3D to_vector(const StepList& lst) {
if (lst.size() >= 3)
return Vector3D(to_double(lst[0]), to_double(lst[1]), to_double(lst[2]));
return Vector3D(0, 0, 1);
}
static int to_ref(const StepValue& v) { return (v.type == StepValue::Type::Ref) ? v.ival : -1; }
static bool is_omitted(const StepValue& v) { return v.type == StepValue::Type::Omitted; }
// ─────────────────────────────────────────────────────────────
// AXIS2_PLACEMENT_3D → coordinate frame
// ─────────────────────────────────────────────────────────────
struct Placement3D {
Point3D origin = Point3D(0, 0, 0);
Vector3D axis = Vector3D(0, 0, 1); // Z-axis (primary)
Vector3D ref_dir = Vector3D(1, 0, 0); // X-axis (secondary, optional)
};
static Placement3D decode_axis2_placement_3d(const StepEntity& ent) {
Placement3D p;
// Args: name, location (CARTESIAN_POINT ref), axis (DIRECTION ref, optional), ref_direction (DIRECTION ref, optional)
if (ent.args.size() >= 2) {
p.origin = to_point({}); // will be resolved later via reference
// Store refs for later resolution
}
return p;
}
// For internal use: we store placement's refs
struct ResolvedPlacement {
Point3D origin = Point3D(0, 0, 0);
Vector3D z_axis = Vector3D(0, 0, 1);
Vector3D x_axis = Vector3D(1, 0, 0);
Vector3D y_axis = Vector3D(0, 1, 0);
};
// ─────────────────────────────────────────────────────────────
// Main converter: entity map → BrepModel(s)
// ─────────────────────────────────────────────────────────────
class StepToBrep {
public:
StepToBrep(const std::unordered_map<int, StepEntity>& entities) : entities_(entities) {}
std::vector<BrepModel> convert() {
std::vector<BrepModel> result;
resolve_all_points();
resolve_all_directions();
resolve_all_placements();
// Find top-level products or directly find MANIFOLD_SOLID_BREP
std::vector<int> root_entities;
// Try finding PRODUCT entities first
for (const auto& [id, ent] : entities_) {
if (ent.type == "PRODUCT" || ent.type == "PRODUCT_DEFINITION") {
root_entities.push_back(id);
}
}
// If no PRODUCT, find SHAPE_DEFINITION_REPRESENTATION or directly MANIFOLD_SOLID_BREP
if (root_entities.empty()) {
for (const auto& [id, ent] : entities_) {
if (ent.type == "SHAPE_DEFINITION_REPRESENTATION") {
root_entities.push_back(id);
}
}
}
// Fallback: find all MANIFOLD_SOLID_BREP and SHELL_BASED_SURFACE_MODEL
std::vector<int> solid_ids;
for (const auto& [id, ent] : entities_) {
if (ent.type == "MANIFOLD_SOLID_BREP" || ent.type == "SHELL_BASED_SURFACE_MODEL") {
solid_ids.push_back(id);
}
}
if (!solid_ids.empty()) {
for (int sid : solid_ids) {
BrepModel body = convert_solid(sid);
if (body.num_faces() > 0) {
// Try to find product name
std::string name = find_product_name(sid);
result.push_back(std::move(body));
}
}
} else if (!root_entities.empty()) {
// Walk PRODUCT → PRODUCT_DEFINITION_FORMATION → ...
for (int rid : root_entities) {
auto bodies = walk_product_tree(rid);
for (auto& b : bodies)
result.push_back(std::move(b));
}
}
return result;
}
private:
const std::unordered_map<int, StepEntity>& entities_;
// Caches
std::unordered_map<int, Point3D> points_;
std::unordered_map<int, Vector3D> directions_;
std::unordered_map<int, ResolvedPlacement> placements_;
std::unordered_map<int, Point3D> vectors_; // VECTOR entities
std::unordered_map<int, curves::NurbsCurve> curves_;
std::unordered_map<int, curves::NurbsSurface> surfaces_;
// ── Resolution helpers ──
void resolve_all_points() {
for (const auto& [id, ent] : entities_) {
if (ent.type == "CARTESIAN_POINT" && ent.args.size() >= 1) {
// Args: name (string), coordinates (list of 3)
if (ent.args.size() >= 2 && ent.args[1].type == StepValue::Type::List) {
points_[id] = to_point(ent.args[1].list);
}
}
}
}
void resolve_all_directions() {
for (const auto& [id, ent] : entities_) {
if (ent.type == "DIRECTION" && ent.args.size() >= 1) {
// Args: name, direction_ratios (list of 2 or 3)
// The list might be args[0] or args[1] depending on schema
const StepList* ratios = nullptr;
if (ent.args[0].type == StepValue::Type::List) ratios = &ent.args[0].list;
else if (ent.args.size() >= 2 && ent.args[1].type == StepValue::Type::List) ratios = &ent.args[1].list;
if (ratios && ratios->size() >= 3) {
directions_[id] = to_vector(*ratios);
} else if (ratios && ratios->size() == 2) {
directions_[id] = Vector3D(to_double((*ratios)[0]), to_double((*ratios)[1]), 0.0);
}
}
}
}
void resolve_all_placements() {
for (const auto& [id, ent] : entities_) {
if (ent.type == "AXIS2_PLACEMENT_3D") {
ResolvedPlacement rp;
// Args sequence depends on schema version, but typically:
// name, location_ref, axis_ref (optional), ref_direction_ref (optional)
if (ent.args.size() >= 2) {
int loc_ref = to_ref(ent.args[1]);
if (loc_ref > 0 && points_.count(loc_ref))
rp.origin = points_[loc_ref];
}
if (ent.args.size() >= 3 && !is_omitted(ent.args[2])) {
int ax_ref = to_ref(ent.args[2]);
if (ax_ref > 0 && directions_.count(ax_ref))
rp.z_axis = directions_[ax_ref].normalized();
}
if (ent.args.size() >= 4 && !is_omitted(ent.args[3])) {
int rd_ref = to_ref(ent.args[3]);
if (rd_ref > 0 && directions_.count(rd_ref))
rp.x_axis = directions_[rd_ref].normalized();
}
// Compute Y = Z × X
// If X is not perpendicular to Z, project it
if (rp.x_axis.dot(rp.z_axis) > 1e-12) {
rp.x_axis = (rp.x_axis - rp.x_axis.dot(rp.z_axis) * rp.z_axis).normalized();
}
rp.y_axis = rp.z_axis.cross(rp.x_axis).normalized();
placements_[id] = rp;
}
}
}
const ResolvedPlacement* get_placement(int ref) {
if (ref > 0 && placements_.count(ref))
return &placements_[ref];
return &default_placement_;
}
static ResolvedPlacement default_placement_;
// ── Curve construction ──
curves::NurbsCurve build_line_curve(int id) {
const auto& ent = entities_.at(id);
// LINE args: name, pnt (CARTESIAN_POINT ref), dir (VECTOR ref)
if (ent.args.size() < 3) throw std::runtime_error("LINE missing args");
int pnt_ref = to_ref(ent.args[1]);
int vec_ref = to_ref(ent.args[2]);
Point3D p = (points_.count(pnt_ref)) ? points_[pnt_ref] : Point3D(0,0,0);
// Eagerly resolve VECTOR entity if not yet cached
Vector3D v = resolve_vector(vec_ref);
std::vector<Point3D> cps = {p, p + v};
return curves::NurbsCurve(cps, {0,0,1,1}, {1,1}, 1);
}
Vector3D resolve_vector(int vec_ref) {
if (vectors_.count(vec_ref)) return vectors_[vec_ref];
if (!entities_.count(vec_ref)) return Vector3D(1, 0, 0);
const auto& vent = entities_.at(vec_ref);
// VECTOR: name, orientation (DIRECTION ref), magnitude
if (vent.args.size() >= 3) {
int dir_ref = to_ref(vent.args[1]);
double mag = to_double(vent.args[2]);
Vector3D d = (directions_.count(dir_ref))
? directions_[dir_ref].normalized()
: Vector3D(1, 0, 0);
Vector3D v = d * mag;
vectors_[vec_ref] = v;
return v;
}
return Vector3D(1, 0, 0);
}
curves::NurbsCurve build_circle_curve(int id) {
const auto& ent = entities_.at(id);
// CIRCLE args: name, placement (AXIS2_PLACEMENT_3D ref), radius
if (ent.args.size() < 3) throw std::runtime_error("CIRCLE missing args");
int pl_ref = to_ref(ent.args[1]);
double R = to_double(ent.args[2]);
const auto* pl = get_placement(pl_ref);
return make_circle_nurbs(pl->origin, pl->x_axis, pl->y_axis, R);
}
curves::NurbsCurve build_ellipse_curve(int id) {
const auto& ent = entities_.at(id);
// ELLIPSE args: name, placement, semi_axis1, semi_axis2
if (ent.args.size() < 4) throw std::runtime_error("ELLIPSE missing args");
int pl_ref = to_ref(ent.args[1]);
double r1 = to_double(ent.args[2]);
double r2 = to_double(ent.args[3]);
const auto* pl = get_placement(pl_ref);
return make_ellipse_nurbs(pl->origin, pl->x_axis, pl->y_axis, r1, r2);
}
curves::NurbsCurve build_bspline_curve(int id) {
const auto& ent = entities_.at(id);
// B_SPLINE_CURVE_WITH_KNOTS args:
// degree, control_points_list, curve_form, closed_curve, self_intersect,
// knot_multiplicities, knots, knot_spec
// Optional: weights_data (last arg)
if (ent.args.size() < 7) throw std::runtime_error("B_SPLINE_CURVE_WITH_KNOTS missing args");
int degree = static_cast<int>(to_double(ent.args[0]));
// Parse control points (ent.args[1] is a list of CARTESIAN_POINT refs)
std::vector<Point3D> cps;
if (ent.args[1].type == StepValue::Type::List) {
for (const auto& v : ent.args[1].list) {
int ref = to_ref(v);
if (ref > 0 && points_.count(ref))
cps.push_back(points_[ref]);
}
}
// Parse knot multiplicities (ent.args[5])
std::vector<int> mults;
if (ent.args[5].type == StepValue::Type::List) {
for (const auto& v : ent.args[5].list)
mults.push_back(static_cast<int>(to_double(v)));
}
// Parse knots (ent.args[6])
std::vector<double> uknots;
if (ent.args[6].type == StepValue::Type::List) {
for (const auto& v : ent.args[6].list)
uknots.push_back(to_double(v));
}
// Expand knot multiplicities into full knot vector
std::vector<double> knots;
for (size_t i = 0; i < uknots.size() && i < mults.size(); ++i) {
for (int j = 0; j < mults[i]; ++j)
knots.push_back(uknots[i]);
}
// Parse weights if provided (last argument)
std::vector<double> weights;
if (ent.args.size() > 7 && ent.args.back().type == StepValue::Type::List
&& !is_omitted(ent.args.back())) {
for (const auto& v : ent.args.back().list)
weights.push_back(to_double(v));
}
if (weights.empty())
weights.assign(cps.size(), 1.0);
// The STEP B-spline may store control points in a different convention
// Enforce degree consistency
if (knots.empty()) {
int n = static_cast<int>(cps.size());
for (int i = 0; i < n + degree + 1; ++i)
knots.push_back(static_cast<double>(i));
}
return curves::NurbsCurve(cps, knots, weights, degree);
}
curves::NurbsCurve build_vector_entity(int id) {
const auto& ent = entities_.at(id);
// VECTOR: name, orientation (DIRECTION ref), magnitude
if (ent.args.size() < 3) throw std::runtime_error("VECTOR missing args");
int dir_ref = to_ref(ent.args[1]);
double mag = to_double(ent.args[2]);
Vector3D d = (directions_.count(dir_ref)) ? directions_[dir_ref] : Vector3D(1,0,0);
d = d.normalized() * mag;
Point3D o(0,0,0);
vectors_[id] = d;
return curves::NurbsCurve({o, o+d}, {0,0,1,1}, {1,1}, 1);
}
// ── Surface construction ──
curves::NurbsSurface build_plane_surface(int id) {
const auto& ent = entities_.at(id);
// PLANE: name, position (AXIS2_PLACEMENT_3D ref)
if (ent.args.size() < 2) throw std::runtime_error("PLANE missing args");
int pl_ref = to_ref(ent.args[1]);
const auto* pl = get_placement(pl_ref);
// Create a large plane as a degree-1×1 NURBS surface
double s = 1e6; // large extent
std::vector<std::vector<Point3D>> grid(2);
grid[0].resize(2); grid[1].resize(2);
grid[0][0] = pl->origin - s*pl->x_axis - s*pl->y_axis;
grid[0][1] = pl->origin - s*pl->x_axis + s*pl->y_axis;
grid[1][0] = pl->origin + s*pl->x_axis - s*pl->y_axis;
grid[1][1] = pl->origin + s*pl->x_axis + s*pl->y_axis;
return curves::NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
curves::NurbsSurface build_cylindrical_surface(int id) {
const auto& ent = entities_.at(id);
// CYLINDRICAL_SURFACE: name, position (AXIS2_PLACEMENT_3D ref), radius
if (ent.args.size() < 3) throw std::runtime_error("CYLINDRICAL_SURFACE missing args");
int pl_ref = to_ref(ent.args[1]);
double R = to_double(ent.args[2]);
const auto* pl = get_placement(pl_ref);
return make_cylinder_surface(pl->origin, pl->z_axis, pl->x_axis, pl->y_axis, R);
}
curves::NurbsSurface build_conical_surface(int id) {
const auto& ent = entities_.at(id);
// CONICAL_SURFACE: name, position, radius, semi_angle
if (ent.args.size() < 4) throw std::runtime_error("CONICAL_SURFACE missing args");
int pl_ref = to_ref(ent.args[1]);
double R = to_double(ent.args[2]);
double angle = to_double(ent.args[3]); // semi-angle in radians
const auto* pl = get_placement(pl_ref);
return make_cone_surface(pl->origin, pl->z_axis, pl->x_axis, pl->y_axis, R, angle);
}
curves::NurbsSurface build_spherical_surface(int id) {
const auto& ent = entities_.at(id);
// SPHERICAL_SURFACE: name, position (AXIS2_PLACEMENT_3D ref), radius
if (ent.args.size() < 3) throw std::runtime_error("SPHERICAL_SURFACE missing args");
int pl_ref = to_ref(ent.args[1]);
double R = to_double(ent.args[2]);
const auto* pl = get_placement(pl_ref);
return make_sphere_surface(pl->origin, pl->z_axis, pl->x_axis, pl->y_axis, R);
}
curves::NurbsSurface build_toroidal_surface(int id) {
const auto& ent = entities_.at(id);
// TOROIDAL_SURFACE: name, position, major_radius, minor_radius
if (ent.args.size() < 4) throw std::runtime_error("TOROIDAL_SURFACE missing args");
int pl_ref = to_ref(ent.args[1]);
double R = to_double(ent.args[2]); // major radius
double r = to_double(ent.args[3]); // minor radius
const auto* pl = get_placement(pl_ref);
return make_torus_surface(pl->origin, pl->z_axis, pl->x_axis, pl->y_axis, R, r);
}
curves::NurbsSurface build_bspline_surface(int id) {
const auto& ent = entities_.at(id);
// B_SPLINE_SURFACE_WITH_KNOTS args:
// u_degree, v_degree, control_points_list (list of lists), surface_form,
// u_closed, v_closed, self_intersect,
// u_mults, v_mults (?), u_knots, v_knots, knot_spec
// Arguments: 0=udeg, 1=vdeg, 2=cps, 3=form, 4=uclosed, 5=vclosed, 6=selfint,
// 7=umults, 8=vmults, 9=uknots, 10=vknots, 11=spec
if (ent.args.size() < 11) throw std::runtime_error("B_SPLINE_SURFACE_WITH_KNOTS missing args");
int du = static_cast<int>(to_double(ent.args[0]));
int dv = static_cast<int>(to_double(ent.args[1]));
// Control points: list of lists of CARTESIAN_POINT refs
std::vector<std::vector<Point3D>> grid;
if (ent.args[2].type == StepValue::Type::List) {
for (const auto& row_val : ent.args[2].list) {
if (row_val.type == StepValue::Type::List) {
std::vector<Point3D> row;
for (const auto& pv : row_val.list) {
int ref = to_ref(pv);
row.push_back((ref > 0 && points_.count(ref)) ? points_[ref] : Point3D(0,0,0));
}
grid.push_back(std::move(row));
}
}
}
// Knots
auto parse_knot_list = [&](const StepValue& mults_v, const StepValue& knots_v) -> std::vector<double> {
std::vector<int> mults;
std::vector<double> uknots;
if (mults_v.type == StepValue::Type::List)
for (const auto& v : mults_v.list) mults.push_back(static_cast<int>(to_double(v)));
if (knots_v.type == StepValue::Type::List)
for (const auto& v : knots_v.list) uknots.push_back(to_double(v));
std::vector<double> result;
for (size_t i = 0; i < uknots.size() && i < mults.size(); ++i)
for (int j = 0; j < mults[i]; ++j) result.push_back(uknots[i]);
return result;
};
std::vector<double> ku = parse_knot_list(ent.args[7], ent.args[9]);
std::vector<double> kv = parse_knot_list(ent.args[8], ent.args[10]);
// Weights (optional)
std::vector<std::vector<double>> wg;
if (ent.args.size() > 12 && ent.args[12].type == StepValue::Type::List) {
for (const auto& row_val : ent.args[12].list) {
if (row_val.type == StepValue::Type::List) {
std::vector<double> wrow;
for (const auto& v : row_val.list) wrow.push_back(to_double(v));
wg.push_back(std::move(wrow));
}
}
}
return curves::NurbsSurface(grid, ku, kv, wg, du, dv);
}
// ── NURBS geometry constructors ──
static curves::NurbsCurve make_circle_nurbs(const Point3D& C,
const Vector3D& X,
const Vector3D& Y, double R) {
double w = std::sqrt(2.0) / 2.0; // cos(π/4)
std::vector<Point3D> cps = {
C + R*X,
C + R*X + R*Y,
C + R*Y,
C - R*X + R*Y,
C - R*X,
C - R*X - R*Y,
C - R*Y,
C + R*X - R*Y,
C + R*X, // closed
};
std::vector<double> wgs = {1, w, 1, w, 1, w, 1, w, 1};
std::vector<double> kts;
for (int i = 0; i < 12; ++i) kts.push_back(i / 4.0); // [0,0,0, 0.25,0.25, 0.5,0.5, 0.75,0.75, 1,1,1] — 12 entries
return curves::NurbsCurve(cps, kts, wgs, 2);
}
static curves::NurbsCurve make_ellipse_nurbs(const Point3D& C,
const Vector3D& X,
const Vector3D& Y, double r1, double r2) {
double w = std::sqrt(2.0) / 2.0;
std::vector<Point3D> cps = {
C + r1*X,
C + r1*X + r2*Y,
C + r2*Y,
C - r1*X + r2*Y,
C - r1*X,
C - r1*X - r2*Y,
C - r2*Y,
C + r1*X - r2*Y,
C + r1*X,
};
std::vector<double> kts;
for (int i = 0; i < 12; ++i) kts.push_back(i / 4.0);
return curves::NurbsCurve(cps, kts, {1,w,1,w,1,w,1,w,1}, 2);
}
static curves::NurbsSurface make_cylinder_surface(const Point3D& C,
const Vector3D& Z,
const Vector3D& X,
const Vector3D& Y, double R) {
// Build the circular profile in the (X,Y) plane
double w = std::sqrt(2.0) / 2.0;
std::vector<Point3D> circ_cps = {
C + R*X, C + R*X + R*Y, C + R*Y,
C - R*X + R*Y, C - R*X, C - R*X - R*Y,
C - R*Y, C + R*X - R*Y, C + R*X,
};
// Build control grid: 2 rows in v (Z direction), 9 cols in u (circle)
// Bottom row: circ_cps at z=0, Top row: circ_cps + large extent in Z
double h = 1e6;
std::vector<std::vector<Point3D>> grid(2);
grid[0] = circ_cps;
for (const auto& p : circ_cps) grid[1].push_back(p + h * Z);
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<std::vector<double>> weights(2);
const std::vector<double> cw = {1, w, 1, w, 1, w, 1, w, 1};
weights[0] = cw;
weights[1] = cw;
return curves::NurbsSurface(grid, ku, kv, weights, 2, 1);
}
static curves::NurbsSurface make_cone_surface(const Point3D& C,
const Vector3D& Z,
const Vector3D& X,
const Vector3D& Y,
double R, double angle) {
double h = 1e6;
double R_top = R + h * std::tan(angle);
double w = std::sqrt(2.0) / 2.0;
auto circle_at = [&](const Point3D& ctr, double r) -> std::vector<Point3D> {
return {
ctr + r*X, ctr + r*X + r*Y, ctr + r*Y,
ctr - r*X + r*Y, ctr - r*X, ctr - r*X - r*Y,
ctr - r*Y, ctr + r*X - r*Y, ctr + r*X,
};
};
std::vector<std::vector<Point3D>> grid = {
circle_at(C, R),
circle_at(C + h * Z, R_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_sphere_surface(const Point3D& C,
const Vector3D& Z,
const Vector3D& X,
const Vector3D& Y,
double R) {
// A sphere as a NURBS surface of revolution about Z
// Profile: a semi-circle (degree 2, 3 control points) in the XZ plane
double w = std::sqrt(2.0) / 2.0;
// Semi-circle profile points: from +Z pole, through equator (+X), to -Z pole
// Actually for sphere of revolution, profile is a half-circle in XZ plane
// The half circle: start at (+R, 0, 0), arc through (R, 0, R)*w to (0,0,R)
// More standard: a rational Bezier half-circle of degree 2:
// P0=(0,0,R) w=1, P1=(R,0,R)*w(?) -- wait let me use the right construction
// Sphere as surface of revolution of half-circle profile about Z:
// We construct 3 rows of control points with the equator as the middle row
// and degenerate poles at top/bottom.
std::vector<std::vector<Point3D>> grid(3);
for (int u = 0; u < 9; ++u) {
double theta = u * (M_PI / 4.0); // 0, π/4, π/2, ..., 2π
double cx = std::cos(theta), sy = std::sin(theta);
Vector3D rad_dir = cx * X + sy * Y;
// Top row (v=0): degenerate at north pole
grid[0].push_back(C + R * Z);
// Middle row (v=1): equator ring
grid[1].push_back(C + R * rad_dir);
// Bottom row (v=2): degenerate at south pole
grid[2].push_back(C - R * Z);
}
// Weights: for proper sphere, each row uses circle arc weights in u
std::vector<std::vector<double>> wg = {
{1, w, 1, w, 1, w, 1, w, 1},
{1, w, 1, w, 1, w, 1, w, 1},
{1, w, 1, w, 1, w, 1, w, 1},
};
// Knot vectors
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};
return curves::NurbsSurface(grid, ku, kv, wg, 2, 2);
}
static curves::NurbsSurface make_torus_surface(const Point3D& C,
const Vector3D& Z,
const Vector3D& X,
const Vector3D& Y,
double R, double r) {
// Torus: circle of radius r swept around circle of radius R
// Control grid: 9×9 for proper torus (degree 2×2)
double w = std::sqrt(2.0) / 2.0;
std::vector<std::vector<Point3D>> grid(9);
std::vector<std::vector<double>> wg(9);
// Sweep minor circle around major circle
for (int u = 0; u < 9; ++u) {
grid[u].resize(9);
wg[u].resize(9);
double theta = u * (M_PI / 4.0); // 0 to 2π
double ct = std::cos(theta), st = std::sin(theta);
Point3D mc = C + R * (ct * X + st * Y); // major circle center
for (int v = 0; v < 9; ++v) {
double phi = v * (M_PI / 4.0);
double cp = std::cos(phi), sp = std::sin(phi);
Vector3D rad = ct * X + st * Y;
grid[u][v] = mc + r * (cp * rad + sp * Z);
// Weight = product of circle arc weights for u and v directions
double wu = (u % 2 == 0) ? 1.0 : w;
double wv = (v % 2 == 0) ? 1.0 : w;
wg[u][v] = wu * wv;
}
}
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);
}
// ── Topological entity conversion ──
BrepModel convert_solid(int solid_id) {
BrepModel model;
const auto& solid_ent = entities_.at(solid_id);
// MANIFOLD_SOLID_BREP args: name, outer (CLOSED_SHELL ref)
// SHELL_BASED_SURFACE_MODEL args: name, sbsm_boundary (SET of SHELL refs)
if (solid_ent.args.size() < 2) return model;
std::string solid_name;
if (!is_omitted(solid_ent.args[0]))
solid_name = to_string(solid_ent.args[0]);
// Look for shell refs — might be a single ref or a list of refs
std::vector<int> shell_refs;
if (solid_ent.args[1].type == StepValue::Type::Ref) {
shell_refs.push_back(to_ref(solid_ent.args[1]));
} else if (solid_ent.args[1].type == StepValue::Type::List) {
for (const auto& sv : solid_ent.args[1].list) {
int r = to_ref(sv);
if (r > 0) shell_refs.push_back(r);
}
}
// Also scan all args for Ref-type shells as fallback
if (shell_refs.empty()) {
for (const auto& arg : solid_ent.args) {
int r = to_ref(arg);
if (r > 0 && entities_.count(r)) {
const auto& e = entities_.at(r);
if (e.type == "CLOSED_SHELL" || e.type == "OPEN_SHELL")
shell_refs.push_back(r);
}
}
}
std::vector<int> all_face_ids;
for (int sh_ref : shell_refs) {
auto face_ids = convert_shell(sh_ref, model);
all_face_ids.insert(all_face_ids.end(), face_ids.begin(), face_ids.end());
}
if (!all_face_ids.empty()) {
int shell_id = model.add_shell(all_face_ids, true);
model.add_body({shell_id}, solid_name);
}
return model;
}
std::vector<int> convert_shell(int shell_id, BrepModel& model) {
std::vector<int> face_ids;
if (!entities_.count(shell_id)) return face_ids;
const auto& shell_ent = entities_.at(shell_id);
// CLOSED_SHELL args: name, cfs_faces (SET of ADVANCED_FACE refs)
if (shell_ent.args.size() < 2) return face_ids;
// The faces set is args[1] (or args[0] if no name)
size_t faces_idx = (shell_ent.args[0].type == StepValue::Type::String) ? 1 : 0;
if (faces_idx >= shell_ent.args.size()) return face_ids;
const StepValue& faces_val = shell_ent.args[faces_idx];
if (faces_val.type != StepValue::Type::List) return face_ids;
for (const auto& fv : faces_val.list) {
int face_ref = to_ref(fv);
if (face_ref > 0) {
int face_id = convert_face(face_ref, model);
if (face_id >= 0) face_ids.push_back(face_id);
}
}
return face_ids;
}
int convert_face(int face_id, BrepModel& model) {
if (!entities_.count(face_id)) return -1;
const auto& face_ent = entities_.at(face_id);
// ADVANCED_FACE args:
// 0: name
// 1: bounds (SET of FACE_BOUND/FACE_OUTER_BOUND refs)
// 2: face_geometry (SURFACE ref)
// 3: same_sense (BOOLEAN)
if (face_ent.args.size() < 3) return -1;
// Resolve surface
int surf_ref = to_ref(face_ent.args[2]);
int surf_id = convert_or_get_surface(surf_ref, model);
if (surf_id < 0) return -1;
// Resolve bounds
std::vector<int> loop_ids;
size_t bounds_idx = 1;
if (face_ent.args[bounds_idx].type == StepValue::Type::List) {
for (const auto& bv : face_ent.args[bounds_idx].list) {
int bound_ref = to_ref(bv);
if (bound_ref > 0) {
int loop_id = convert_face_bound(bound_ref, model);
if (loop_id >= 0) loop_ids.push_back(loop_id);
}
}
}
if (loop_ids.empty()) return -1;
return model.add_face(surf_id, loop_ids);
}
int convert_face_bound(int bound_id, BrepModel& model) {
if (!entities_.count(bound_id)) return -1;
const auto& ent = entities_.at(bound_id);
// FACE_BOUND / FACE_OUTER_BOUND args:
// 0: name
// 1: bound (EDGE_LOOP ref)
// 2: orientation (BOOLEAN)
bool is_outer = (ent.type == "FACE_OUTER_BOUND");
int loop_ref = -1;
for (const auto& arg : ent.args) {
int r = to_ref(arg);
if (r > 0 && entities_.count(r) && entities_.at(r).type == "EDGE_LOOP") {
loop_ref = r;
break;
}
}
if (loop_ref < 0) return -1;
return convert_edge_loop(loop_ref, model, is_outer);
}
int convert_edge_loop(int loop_id, BrepModel& model, bool is_outer) {
if (!entities_.count(loop_id)) return -1;
const auto& ent = entities_.at(loop_id);
// EDGE_LOOP args: name, edge_list (SET of ORIENTED_EDGE refs)
const StepValue* edge_list = nullptr;
for (const auto& arg : ent.args) {
if (arg.type == StepValue::Type::List) {
edge_list = &arg;
break;
}
}
if (!edge_list) return -1;
std::vector<int> edge_indices;
for (const auto& ev : edge_list->list) {
int oe_ref = to_ref(ev);
if (oe_ref > 0) {
int ei = convert_oriented_edge(oe_ref, model);
if (ei >= 0) edge_indices.push_back(ei);
}
}
if (edge_indices.empty()) return -1;
return model.add_loop(edge_indices, is_outer);
}
int convert_oriented_edge(int oe_id, BrepModel& model) {
if (!entities_.count(oe_id)) return -1;
const auto& ent = entities_.at(oe_id);
// ORIENTED_EDGE args:
// 0: name, 1: edge_element (EDGE_CURVE ref), 2: orientation
int edge_ref = -1;
bool reversed = false;
for (const auto& arg : ent.args) {
int r = to_ref(arg);
if (r > 0 && entities_.count(r) && (entities_.at(r).type == "EDGE_CURVE" || entities_.at(r).type == "EDGE_CURVE_IMPLICIT")) {
edge_ref = r;
}
}
// Check orientation: arg[2] is T/F boolean
if (ent.args.size() >= 3 && ent.args[2].type == StepValue::Type::Enum) {
reversed = (ent.args[2].sval == ".F.");
}
if (edge_ref < 0) return -1;
return convert_edge_curve(edge_ref, model, reversed);
}
int convert_edge_curve(int edge_id, BrepModel& model, bool reversed) {
if (!entities_.count(edge_id)) return -1;
const auto& ent = entities_.at(edge_id);
// EDGE_CURVE args:
// 0: name
// 1: edge_start (VERTEX_POINT ref)
// 2: edge_end (VERTEX_POINT ref)
// 3: edge_geometry (CURVE ref)
// 4: same_sense (BOOLEAN)
if (ent.args.size() < 5) return -1;
int v_s = convert_vertex_point(to_ref(ent.args[1]), model);
int v_e = convert_vertex_point(to_ref(ent.args[2]), model);
int curve_ref = to_ref(ent.args[3]);
curves::NurbsCurve nc = get_or_build_curve(curve_ref);
return model.add_edge(v_s, v_e, nc);
}
int convert_vertex_point(int vp_id, BrepModel& model) {
if (vp_cache_.count(vp_id)) return vp_cache_[vp_id];
if (!entities_.count(vp_id)) return -1;
const auto& ent = entities_.at(vp_id);
// VERTEX_POINT args: name, vertex_geometry (CARTESIAN_POINT ref)
int pt_ref = -1;
for (const auto& arg : ent.args) {
pt_ref = to_ref(arg);
if (pt_ref > 0 && points_.count(pt_ref)) break;
}
Point3D pt = (pt_ref > 0 && points_.count(pt_ref)) ? points_[pt_ref] : Point3D(0,0,0);
int vi = model.add_vertex(pt);
vp_cache_[vp_id] = vi;
return vi;
}
int convert_or_get_surface(int surf_ref, BrepModel& model) {
if (surf_cache_.count(surf_ref)) return surf_cache_[surf_ref];
curves::NurbsSurface surf = get_or_build_surface(surf_ref);
int si = model.add_surface(surf);
surf_cache_[surf_ref] = si;
return si;
}
curves::NurbsCurve get_or_build_curve(int ref) {
auto cit = curve_cache_.find(ref);
if (cit != curve_cache_.end()) return cit->second;
if (!entities_.count(ref)) {
// Return a dummy curve
return curves::NurbsCurve({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
}
const auto& ent = entities_.at(ref);
// Default-construct with dummy line (NurbsCurve has no default ctor)
curves::NurbsCurve nc({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
try {
if (ent.type == "LINE")
nc = build_line_curve(ref);
else if (ent.type == "CIRCLE")
nc = build_circle_curve(ref);
else if (ent.type == "ELLIPSE")
nc = build_ellipse_curve(ref);
else if (ent.type == "B_SPLINE_CURVE_WITH_KNOTS")
nc = build_bspline_curve(ref);
else if (ent.type == "VECTOR")
nc = build_vector_entity(ref);
else
nc = curves::NurbsCurve({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
} catch (const std::exception& e) {
set_error(StepError::EntityTypeError, "Error building curve #" + std::to_string(ref) + ": " + e.what());
nc = curves::NurbsCurve({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
}
curve_cache_.insert_or_assign(ref, nc);
return nc;
}
curves::NurbsSurface get_or_build_surface(int ref) {
auto sit = surface_cache_.find(ref);
if (sit != surface_cache_.end()) return sit->second;
if (!entities_.count(ref)) {
// Return a dummy surface
std::vector<std::vector<Point3D>> g = {{Point3D(0,0,0), Point3D(1,0,0)},
{Point3D(0,1,0), Point3D(1,1,0)}};
return curves::NurbsSurface(g, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
const auto& ent = entities_.at(ref);
// Default-construct with a dummy surface (NurbsSurface has no default ctor)
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 {
if (ent.type == "PLANE")
ns = build_plane_surface(ref);
else if (ent.type == "CYLINDRICAL_SURFACE")
ns = build_cylindrical_surface(ref);
else if (ent.type == "CONICAL_SURFACE")
ns = build_conical_surface(ref);
else if (ent.type == "SPHERICAL_SURFACE")
ns = build_spherical_surface(ref);
else if (ent.type == "TOROIDAL_SURFACE")
ns = build_toroidal_surface(ref);
else if (ent.type == "B_SPLINE_SURFACE_WITH_KNOTS")
ns = build_bspline_surface(ref);
else {
std::vector<std::vector<Point3D>> g = {{Point3D(0,0,0), Point3D(1,0,0)},
{Point3D(0,1,0), Point3D(1,1,0)}};
ns = curves::NurbsSurface(g, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
} catch (const std::exception& e) {
set_error(StepError::EntityTypeError, "Error building surface #" + std::to_string(ref) + ": " + e.what());
std::vector<std::vector<Point3D>> g = {{Point3D(0,0,0), Point3D(1,0,0)},
{Point3D(0,1,0), Point3D(1,1,0)}};
ns = curves::NurbsSurface(g, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
surface_cache_.insert_or_assign(ref, ns);
return ns;
}
// ── Product tree walk (for assembly) ──
std::string find_product_name(int solid_id) {
// Walk back through the entity graph to find PRODUCT name
// This is a heuristic
for (const auto& [id, ent] : entities_) {
if (ent.type == "PRODUCT") {
// PRODUCT args: name, description, id, ...
if (ent.args.size() >= 1 && ent.args[0].type == StepValue::Type::String)
return ent.args[0].sval;
}
}
(void)solid_id;
return "";
}
std::vector<BrepModel> walk_product_tree(int root_id) {
std::vector<BrepModel> result;
if (!entities_.count(root_id)) return result;
const auto& ent = entities_.at(root_id);
// SHAPE_DEFINITION_REPRESENTATION → find MANIFOLD_SOLID_BREP refs
std::vector<int> solid_refs;
find_nested_refs(ent, "MANIFOLD_SOLID_BREP", solid_refs);
find_nested_refs(ent, "SHELL_BASED_SURFACE_MODEL", solid_refs);
if (solid_refs.empty()) {
// Try just iterating all args
collect_all_refs(ent, solid_refs);
// Filter to solids
std::vector<int> filtered;
for (int r : solid_refs) {
if (entities_.count(r)) {
const auto& e = entities_.at(r);
if (e.type == "MANIFOLD_SOLID_BREP" || e.type == "SHELL_BASED_SURFACE_MODEL")
filtered.push_back(r);
}
}
solid_refs = filtered;
}
for (int sid : solid_refs) {
auto body = convert_solid(sid);
if (body.num_faces() > 0) result.push_back(std::move(body));
}
return result;
}
void find_nested_refs(const StepEntity& ent, const std::string& target_type, std::vector<int>& out) {
for (const auto& arg : ent.args) {
if (arg.type == StepValue::Type::Ref) {
int r = arg.ival;
if (entities_.count(r)) {
if (entities_.at(r).type == target_type)
out.push_back(r);
else
find_nested_refs(entities_.at(r), target_type, out);
}
} else if (arg.type == StepValue::Type::List) {
for (const auto& v : arg.list) {
StepEntity dummy;
dummy.args = {v};
find_nested_refs(dummy, target_type, out);
}
}
}
}
void collect_all_refs(const StepEntity& ent, std::vector<int>& out) {
for (const auto& arg : ent.args) {
if (arg.type == StepValue::Type::Ref)
out.push_back(arg.ival);
else if (arg.type == StepValue::Type::List)
for (const auto& v : arg.list)
if (v.type == StepValue::Type::Ref) out.push_back(v.ival);
}
}
// Cache maps
std::unordered_map<int, int> vp_cache_; // vertex_point_id → BrepModel vertex index
std::unordered_map<int, int> surf_cache_; // surface entity id → BrepModel surface index
std::unordered_map<int, curves::NurbsCurve> curve_cache_;
std::unordered_map<int, curves::NurbsSurface> surface_cache_;
};
ResolvedPlacement StepToBrep::default_placement_;
// ─────────────────────────────────────────────────────────────
// Public API
// ─────────────────────────────────────────────────────────────
std::vector<BrepModel> import_step_from_string(const std::string& step_data) {
clear_error();
if (step_data.empty()) {
set_error(StepError::ParseError, "Empty STEP data");
return {};
}
StepParser parser(step_data);
if (!parser.parse_data_section()) {
return {};
}
StepToBrep converter(parser.entities());
try {
return converter.convert();
} catch (const std::exception& e) {
set_error(StepError::ParseError, std::string("Conversion error: ") + e.what());
return {};
}
}
std::vector<BrepModel> import_step(const std::string& filepath) {
clear_error();
std::ifstream file(filepath);
if (!file.is_open()) {
set_error(StepError::FileNotFound, "Cannot open file: " + filepath);
return {};
}
std::ostringstream buf;
buf << file.rdbuf();
return import_step_from_string(buf.str());
}
} // namespace vde::brep