Add configurable GLB node naming

This commit is contained in:
sladro 2026-04-24 09:47:27 +08:00
parent 70ccc02cc4
commit 4d0af82aab
14 changed files with 490 additions and 12 deletions

View File

@ -83,6 +83,7 @@ set(SOURCES
src/cadit/occt/debug.cpp src/cadit/occt/debug.cpp
src/cadit/occt/gltf_writer.cpp src/cadit/occt/gltf_writer.cpp
src/cadit/occt/convert.cpp src/cadit/occt/convert.cpp
src/cadit/occt/node_naming.cpp
src/cadit/occt/step_helpers.cpp src/cadit/occt/step_helpers.cpp
src/cadit/occt/bsplinesurf.cpp src/cadit/occt/bsplinesurf.cpp
src/cadit/occt/helpers.cpp src/cadit/occt/helpers.cpp
@ -98,6 +99,7 @@ set(HEADERS
src/geom/Color.h src/geom/Color.h
src/cadit/occt/step_tree.h src/cadit/occt/step_tree.h
src/cadit/occt/convert.h src/cadit/occt/convert.h
src/cadit/occt/node_naming.h
src/cadit/occt/debug.h src/cadit/occt/debug.h
src/cadit/occt/step_helpers.h src/cadit/occt/step_helpers.h
src/cadit/occt/gltf_writer.h src/cadit/occt/gltf_writer.h
@ -142,4 +144,3 @@ if (BUILD_TESTING)
enable_testing() enable_testing()
include(tests/tests.cmake) include(tests/tests.cmake)
endif (BUILD_TESTING) endif (BUILD_TESTING)

4
desktop/tmp-render.ts Normal file
View File

@ -0,0 +1,4 @@
import { renderToStaticMarkup } from "react-dom/server";
import App from "./src/App";
console.log(renderToStaticMarkup(<App />));

View File

@ -29,6 +29,7 @@
#include "custom_progress.h" #include "custom_progress.h"
#include "geometry_iterator.h" #include "geometry_iterator.h"
#include "node_naming.h"
#include "step_helpers.h" #include "step_helpers.h"
#include "step_tree.h" #include "step_tree.h"
#include "../../config_structs.h" #include "../../config_structs.h"
@ -262,6 +263,9 @@ void convert_stp_to_glb(const GlobalConfig& config)
std::cout << "Reading STEP file: " << config.stpFile << std::endl; std::cout << "Reading STEP file: " << config.stpFile << std::endl;
if (reader.ReadFile(config.stpFile.string().c_str()) != IFSelect_RetDone) if (reader.ReadFile(config.stpFile.string().c_str()) != IFSelect_RetDone)
throw std::runtime_error("Error reading STEP file"); throw std::runtime_error("Error reading STEP file");
auto default_reader = reader.ChangeReader();
auto model = default_reader.StepModel();
Interface_Graph theGraph(model, /*keepTransient*/ Standard_False);
auto stop = std::chrono::high_resolution_clock::now(); auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration<double>(stop - start).count(); auto duration = std::chrono::duration<double>(stop - start).count();
@ -334,6 +338,7 @@ void convert_stp_to_glb(const GlobalConfig& config)
throw std::runtime_error("Error writing GLB file"); throw std::runtime_error("Error writing GLB file");
} }
apply_node_name_mode_to_glb(config.glbFile, model, theGraph, config.nodeNameMode);
apply_world_translation_to_glb(config.glbFile, root_translation); apply_world_translation_to_glb(config.glbFile, root_translation);
stop = std::chrono::high_resolution_clock::now(); stop = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration<double>(stop - start).count(); duration = std::chrono::duration<double>(stop - start).count();
@ -354,4 +359,3 @@ void convert_stp_to_glb(const GlobalConfig& config)
log_file << "]\n"; log_file << "]\n";
log_file.close(); log_file.close();
} }

View File

@ -106,7 +106,7 @@ void debug_stp_to_glb(const GlobalConfig &config) {
std::cout << "Number of entities: " << num_entities << "\n"; std::cout << "Number of entities: " << num_entities << "\n";
// Extract hierarchy // Extract hierarchy
auto roots = ExtractProductHierarchy(model, theGraph); auto roots = ExtractProductHierarchy(model, theGraph, config.nodeNameMode);
add_geometries_to_nodes(roots, theGraph); add_geometries_to_nodes(roots, theGraph);
auto step_store = StepStore(roots); auto step_store = StepStore(roots);

View File

@ -0,0 +1,306 @@
#include "node_naming.h"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <StepBasic_ProductDefinitionFormation.hxx>
#include <TCollection_HAsciiString.hxx>
#include <nlohmann/json.hpp>
#include "step_tree.h"
namespace {
std::string trim_copy(std::string value)
{
auto not_space = [](unsigned char c) { return !std::isspace(c); };
value.erase(value.begin(), std::find_if(value.begin(), value.end(), not_space));
value.erase(std::find_if(value.rbegin(), value.rend(), not_space).base(), value.end());
return value;
}
std::string ascii_lower(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return value;
}
std::string to_string(const Handle(TCollection_HAsciiString)& value)
{
if (value.IsNull()) {
return {};
}
return trim_copy(value->ToCString());
}
std::string title_with_version(const NodeNameParts& parts)
{
if (parts.title.empty()) {
return parts.version;
}
if (parts.version.empty()) {
return parts.title;
}
return parts.title + " " + parts.version;
}
void write_updated_glb_json(const std::filesystem::path& glb_path,
const std::vector<char>& data,
const std::size_t json_data_offset,
const std::uint32_t old_json_length,
const nlohmann::json& document)
{
constexpr std::uint32_t kJsonChunkType = 0x4E4F534A;
std::string updated_json = document.dump();
const std::size_t padded_json_length = (updated_json.size() + 3) & ~std::size_t(3);
updated_json.resize(padded_json_length, ' ');
const std::size_t json_chunk_total_size = sizeof(std::uint32_t) * 2 + padded_json_length;
const std::size_t remaining_size = data.size() - (json_data_offset + old_json_length);
const std::size_t new_file_size = sizeof(std::uint32_t) * 3 + json_chunk_total_size + remaining_size;
std::vector<char> output;
output.reserve(new_file_size);
output.insert(output.end(), data.begin(), data.begin() + sizeof(std::uint32_t) * 3);
const std::uint32_t new_length = static_cast<std::uint32_t>(new_file_size);
std::memcpy(output.data() + sizeof(std::uint32_t) * 2, &new_length, sizeof(std::uint32_t));
const std::uint32_t new_json_length = static_cast<std::uint32_t>(padded_json_length);
const char* json_length_bytes = reinterpret_cast<const char*>(&new_json_length);
output.insert(output.end(), json_length_bytes, json_length_bytes + sizeof(std::uint32_t));
const char* chunk_type_bytes = reinterpret_cast<const char*>(&kJsonChunkType);
output.insert(output.end(), chunk_type_bytes, chunk_type_bytes + sizeof(std::uint32_t));
output.insert(output.end(), updated_json.begin(), updated_json.end());
output.insert(output.end(), data.begin() + json_data_offset + old_json_length, data.end());
std::ofstream out_file(glb_path, std::ios::binary | std::ios::trunc);
if (!out_file) {
throw std::runtime_error("Failed to open GLB for node name update: " + glb_path.string());
}
out_file.write(output.data(), static_cast<std::streamsize>(output.size()));
}
int default_scene_index(const nlohmann::json& document)
{
if (document.contains("scene") && document["scene"].is_number_integer()) {
return document["scene"].get<int>();
}
return 0;
}
bool apply_product_name_tree_to_gltf_node(nlohmann::json& document,
int node_index,
const ProductNode& product_node)
{
if (!document.contains("nodes") || !document["nodes"].is_array() ||
node_index < 0 || node_index >= static_cast<int>(document["nodes"].size())) {
return false;
}
bool changed = false;
auto& gltf_node = document["nodes"][node_index];
if (!product_node.name.empty() &&
(!gltf_node.contains("name") || !gltf_node["name"].is_string() ||
gltf_node["name"].get<std::string>() != product_node.name)) {
gltf_node["name"] = product_node.name;
changed = true;
}
if (!gltf_node.contains("children") || !gltf_node["children"].is_array()) {
return changed;
}
const auto& children = gltf_node["children"];
const std::size_t count = std::min(children.size(), product_node.children.size());
if (children.size() != product_node.children.size()) {
std::cerr << "Warning: GLB node child count does not match STEP hierarchy for node: "
<< product_node.name << "\n";
}
for (std::size_t i = 0; i < count; ++i) {
if (!children[i].is_number_integer() || !product_node.children[i]) {
continue;
}
changed = apply_product_name_tree_to_gltf_node(document,
children[i].get<int>(),
*product_node.children[i]) || changed;
}
return changed;
}
bool apply_product_name_tree_to_gltf(nlohmann::json& document,
const std::vector<std::unique_ptr<ProductNode>>& roots)
{
if (roots.empty() || !document.contains("scenes") || !document["scenes"].is_array()) {
return false;
}
const int scene_index = default_scene_index(document);
if (scene_index < 0 || scene_index >= static_cast<int>(document["scenes"].size())) {
return false;
}
auto& scene = document["scenes"][scene_index];
if (!scene.contains("nodes") || !scene["nodes"].is_array()) {
return false;
}
const auto& scene_nodes = scene["nodes"];
const std::size_t count = std::min(scene_nodes.size(), roots.size());
if (scene_nodes.size() != roots.size()) {
std::cerr << "Warning: GLB root node count does not match STEP hierarchy\n";
}
bool changed = false;
for (std::size_t i = 0; i < count; ++i) {
if (!scene_nodes[i].is_number_integer() || !roots[i]) {
continue;
}
changed = apply_product_name_tree_to_gltf_node(document,
scene_nodes[i].get<int>(),
*roots[i]) || changed;
}
return changed;
}
} // namespace
NodeNameMode parse_node_name_mode(const std::string& value)
{
const std::string normalized = ascii_lower(trim_copy(value));
if (normalized.empty() || normalized == "source") {
return NodeNameMode::Source;
}
if (normalized == "instance") {
return NodeNameMode::Instance;
}
if (normalized == "title") {
return NodeNameMode::Title;
}
if (normalized == "title-version" || normalized == "title_version") {
return NodeNameMode::TitleVersion;
}
if (normalized == "combined") {
return NodeNameMode::Combined;
}
throw std::invalid_argument("Invalid --node-name-mode value: " + value);
}
std::string format_node_name(NodeNameMode mode, const NodeNameParts& parts)
{
const std::string title_version = title_with_version(parts);
switch (mode) {
case NodeNameMode::Source:
return {};
case NodeNameMode::Instance:
return !parts.instance_title.empty() ? parts.instance_title : title_version;
case NodeNameMode::Title:
return !parts.title.empty() ? parts.title : (!parts.instance_title.empty() ? parts.instance_title : parts.version);
case NodeNameMode::TitleVersion:
return !title_version.empty() ? title_version : parts.instance_title;
case NodeNameMode::Combined:
if (!parts.instance_title.empty() && !parts.title.empty() && parts.instance_title != parts.title) {
return parts.instance_title + " (" + parts.title + ")";
}
return !parts.instance_title.empty() ? parts.instance_title : title_version;
}
return {};
}
NodeNameParts make_node_name_parts(const Handle(StepBasic_Product)& product,
const Handle(StepBasic_ProductDefinition)& product_definition,
const Handle(StepRepr_NextAssemblyUsageOccurrence)& occurrence)
{
NodeNameParts parts;
if (!occurrence.IsNull()) {
parts.instance_title = to_string(occurrence->Id());
if (parts.instance_title.empty()) {
parts.instance_title = to_string(occurrence->Name());
}
if (parts.instance_title.empty() && occurrence->HasReferenceDesignator()) {
parts.instance_title = to_string(occurrence->ReferenceDesignator());
}
}
if (!product.IsNull()) {
parts.title = to_string(product->Name());
}
if (!product_definition.IsNull() && !product_definition->Formation().IsNull()) {
parts.version = to_string(product_definition->Formation()->Id());
}
return parts;
}
void apply_node_name_mode_to_glb(const std::filesystem::path& glb_path,
const Handle(Interface_InterfaceModel)& model,
const Interface_Graph& graph,
NodeNameMode mode)
{
if (mode == NodeNameMode::Source || model.IsNull()) {
return;
}
constexpr std::uint32_t kGlbMagic = 0x46546C67;
constexpr std::uint32_t kJsonChunkType = 0x4E4F534A;
std::ifstream input(glb_path, std::ios::binary);
if (!input) {
throw std::runtime_error("Failed to open GLB for node name update: " + glb_path.string());
}
input.seekg(0, std::ios::end);
const std::streamsize file_size = input.tellg();
input.seekg(0, std::ios::beg);
std::vector<char> data(static_cast<std::size_t>(file_size));
input.read(data.data(), file_size);
auto read_u32 = [](const char* ptr) {
std::uint32_t value;
std::memcpy(&value, ptr, sizeof(std::uint32_t));
return value;
};
if (data.size() < sizeof(std::uint32_t) * 5 || read_u32(data.data()) != kGlbMagic) {
throw std::runtime_error("Invalid GLB file for node name update: " + glb_path.string());
}
const std::size_t json_chunk_header_offset = sizeof(std::uint32_t) * 3;
const std::uint32_t json_length = read_u32(data.data() + json_chunk_header_offset);
const std::uint32_t json_type = read_u32(data.data() + json_chunk_header_offset + sizeof(std::uint32_t));
if (json_type != kJsonChunkType) {
throw std::runtime_error("Unexpected GLB JSON chunk for node name update: " + glb_path.string());
}
const std::size_t json_data_offset = json_chunk_header_offset + sizeof(std::uint32_t) * 2;
if (json_data_offset + json_length > data.size()) {
throw std::runtime_error("Invalid GLB JSON length for node name update: " + glb_path.string());
}
const std::string json_text(data.data() + json_data_offset, data.data() + json_data_offset + json_length);
nlohmann::json document = nlohmann::json::parse(json_text);
auto roots = ExtractProductHierarchy(model, graph, mode);
const bool changed = apply_product_name_tree_to_gltf(document, roots);
if (changed) {
write_updated_glb_json(glb_path, data, json_data_offset, json_length, document);
}
}

View File

@ -0,0 +1,32 @@
#ifndef STP2GLB_NODE_NAMING_H
#define STP2GLB_NODE_NAMING_H
#include <filesystem>
#include <string>
#include <Interface_Graph.hxx>
#include <Interface_InterfaceModel.hxx>
#include <Standard_Handle.hxx>
#include <StepBasic_Product.hxx>
#include <StepBasic_ProductDefinition.hxx>
#include <StepRepr_NextAssemblyUsageOccurrence.hxx>
#include "../../config_structs.h"
struct NodeNameParts {
std::string instance_title;
std::string title;
std::string version;
};
NodeNameMode parse_node_name_mode(const std::string& value);
std::string format_node_name(NodeNameMode mode, const NodeNameParts& parts);
NodeNameParts make_node_name_parts(const Handle(StepBasic_Product)& product,
const Handle(StepBasic_ProductDefinition)& product_definition,
const Handle(StepRepr_NextAssemblyUsageOccurrence)& occurrence);
void apply_node_name_mode_to_glb(const std::filesystem::path& glb_path,
const Handle(Interface_InterfaceModel)& model,
const Interface_Graph& graph,
NodeNameMode mode);
#endif // STP2GLB_NODE_NAMING_H

View File

@ -3,6 +3,7 @@
#include <Interface_EntityIterator.hxx> #include <Interface_EntityIterator.hxx>
// STEP entity classes // STEP entity classes
#include <algorithm>
#include <StepBasic_Product.hxx> #include <StepBasic_Product.hxx>
#include <StepBasic_ProductDefinition.hxx> #include <StepBasic_ProductDefinition.hxx>
#include <StepBasic_ProductDefinitionFormation.hxx> #include <StepBasic_ProductDefinitionFormation.hxx>
@ -30,6 +31,7 @@
#include <StepGeom_CartesianPoint.hxx> #include <StepGeom_CartesianPoint.hxx>
#include <StepGeom_Direction.hxx> #include <StepGeom_Direction.hxx>
#include "node_naming.h"
#include "step_helpers.h" #include "step_helpers.h"
@ -343,7 +345,8 @@ BuildAssemblyLinksWithTransformation(const Handle(Interface_InterfaceModel) &mod
int ProductNode::instanceCounter = 0; int ProductNode::instanceCounter = 0;
// Main function: extracts top-level ProductNode trees with transformations // Main function: extracts top-level ProductNode trees with transformations
std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model, std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph) { const Interface_Graph &theGraph,
NodeNameMode node_name_mode) {
// 1) Build the map of parent->children relationships // 1) Build the map of parent->children relationships
// to be replaced by this // to be replaced by this
const auto parentToChildrenWTransforms = BuildAssemblyLinksWithTransformation(model, theGraph); const auto parentToChildrenWTransforms = BuildAssemblyLinksWithTransformation(model, theGraph);
@ -362,6 +365,7 @@ std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(
for (auto productIndexMap = BuildProductIndexMap(model); auto &val: productIndexMap | std::views::values) { for (auto productIndexMap = BuildProductIndexMap(model); auto &val: productIndexMap | std::views::values) {
allProducts.push_back(val); allProducts.push_back(val);
} }
std::sort(allProducts.begin(), allProducts.end());
// 4) For each product, if its NOT in allChildren => its a root // 4) For each product, if its NOT in allChildren => its a root
std::vector<std::unique_ptr<ProductNode> > roots; std::vector<std::unique_ptr<ProductNode> > roots;
@ -369,7 +373,8 @@ std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(
if (!allChildren.contains(idx)) { if (!allChildren.contains(idx)) {
// This is a root product, start with the identity transformation // This is a root product, start with the identity transformation
roots.push_back( roots.push_back(
BuildProductNodeWithTransform(idx, parentToChildrenWTransforms, model, theGraph, gp_Trsf())); BuildProductNodeWithTransform(idx, parentToChildrenWTransforms, model, theGraph, gp_Trsf(), nullptr,
node_name_mode));
} }
} }
return roots; return roots;
@ -447,7 +452,9 @@ static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
const Handle(Interface_InterfaceModel) &model, const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph, const Interface_Graph &theGraph,
const gp_Trsf &parentTransform, const gp_Trsf &parentTransform,
ProductNode *parent) { ProductNode *parent,
NodeNameMode node_name_mode,
const ParentChildRelationship* relationship) {
auto node = std::make_unique<ProductNode>(); auto node = std::make_unique<ProductNode>();
node->entityIndex = productIndex; node->entityIndex = productIndex;
node->parent = parent; node->parent = parent;
@ -455,10 +462,29 @@ static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
const Handle(Standard_Transient) ent = model->Value(productIndex); const Handle(Standard_Transient) ent = model->Value(productIndex);
const auto product = Handle(StepBasic_Product)::DownCast(ent); const auto product = Handle(StepBasic_Product)::DownCast(ent);
if (!product.IsNull() && !product->Name().IsNull()) { if (node_name_mode != NodeNameMode::Source && !product.IsNull()) {
Handle(StepRepr_NextAssemblyUsageOccurrence) occurrence;
if (relationship != nullptr && relationship->nauoIndex > 0 && relationship->nauoIndex <= model->NbEntities()) {
occurrence = Handle(StepRepr_NextAssemblyUsageOccurrence)::DownCast(model->Value(relationship->nauoIndex));
}
Handle(StepBasic_ProductDefinition) product_definition;
if (!occurrence.IsNull()) {
product_definition = occurrence->RelatedProductDefinition();
} else {
product_definition = FindProductDefinition(product, model, theGraph);
}
node->name = format_node_name(node_name_mode,
make_node_name_parts(product, product_definition, occurrence));
}
if (node->name.empty() && !product.IsNull() && !product->Name().IsNull()) {
node->name = product->Name()->ToCString(); node->name = product->Name()->ToCString();
} else { } else {
node->name = "(unnamed product)"; if (node->name.empty()) {
node->name = "(unnamed product)";
}
} }
// Combine parent transformation with local transformation // Combine parent transformation with local transformation
@ -477,7 +503,7 @@ static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
// Build the child node // Build the child node
node->children.push_back( node->children.push_back(
BuildProductNodeWithTransform(childRel.childIndex, parentToChildrenWTransforms, model, theGraph, BuildProductNodeWithTransform(childRel.childIndex, parentToChildrenWTransforms, model, theGraph,
childAbsoluteTransform, node.get())); childAbsoluteTransform, node.get(), node_name_mode, &childRel));
} }
} }

View File

@ -16,6 +16,8 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include "../../config_structs.h"
struct ProcessResult { struct ProcessResult {
mutable bool added_to_model; mutable bool added_to_model;
mutable std::string skip_reason; mutable std::string skip_reason;
@ -70,7 +72,8 @@ struct ParentChildRelationship {
}; };
std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model, std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph); const Interface_Graph &theGraph,
NodeNameMode node_name_mode = NodeNameMode::Source);
std::string ExportHierarchyToJson(const std::vector<std::unique_ptr<ProductNode> > &roots); std::string ExportHierarchyToJson(const std::vector<std::unique_ptr<ProductNode> > &roots);
@ -86,7 +89,9 @@ static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
const Handle(Interface_InterfaceModel)& model, const Handle(Interface_InterfaceModel)& model,
const Interface_Graph& theGraph, const Interface_Graph& theGraph,
const gp_Trsf& parentTransform = gp_Trsf(), const gp_Trsf& parentTransform = gp_Trsf(),
ProductNode* parent = nullptr); ProductNode* parent = nullptr,
NodeNameMode node_name_mode = NodeNameMode::Source,
const ParentChildRelationship* relationship = nullptr);
static std::unique_ptr<ProductNode> BuildProductNodeWithTransformIterative( static std::unique_ptr<ProductNode> BuildProductNodeWithTransformIterative(
int rootIndex, int rootIndex,

View File

@ -9,6 +9,14 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
enum class NodeNameMode {
Source,
Instance,
Title,
TitleVersion,
Combined
};
struct BuildConfig { struct BuildConfig {
bool build_bspline_surf = false; bool build_bspline_surf = false;
}; };
@ -40,6 +48,7 @@ struct GlobalConfig {
std::vector<std::string> filter_names_include; std::vector<std::string> filter_names_include;
std::vector<std::string> filter_names_exclude; std::vector<std::string> filter_names_exclude;
NodeNameMode nodeNameMode = NodeNameMode::Source;
BuildConfig buildConfig; BuildConfig buildConfig;
ServerConfig serverConfig; ServerConfig serverConfig;

View File

@ -11,6 +11,7 @@
#include "config_structs.h" #include "config_structs.h"
#include "cadit/occt/helpers.h" #include "cadit/occt/helpers.h"
#include "cadit/occt/node_naming.h"
#include "CLI/App.hpp" #include "CLI/App.hpp"
#include "http_downloader.h" #include "http_downloader.h"
@ -84,6 +85,7 @@ GlobalConfig process_parameters_internal(CLI::App& app,
const auto compressed_glb_value = app.get_option("--compressed-glb")->as<std::string>(); const auto compressed_glb_value = app.get_option("--compressed-glb")->as<std::string>();
const auto gltfpack_path_value = app.get_option("--gltfpack-path")->as<std::string>(); const auto gltfpack_path_value = app.get_option("--gltfpack-path")->as<std::string>();
const auto gltfpack_args_value = app.get_option("--gltfpack-args")->as<std::string>(); const auto gltfpack_args_value = app.get_option("--gltfpack-args")->as<std::string>();
const auto node_name_mode_value = app.get_option("--node-name-mode")->as<std::string>();
// Process include and exclude filter names // Process include and exclude filter names
const auto filter_names_include = process_filter_names(filter_names_include_input, filter_names_file_include); const auto filter_names_include = process_filter_names(filter_names_include_input, filter_names_file_include);
@ -148,6 +150,7 @@ GlobalConfig process_parameters_internal(CLI::App& app,
.tessellation_timout = app.get_option("--tessellation-timeout")->as<int>(), .tessellation_timout = app.get_option("--tessellation-timeout")->as<int>(),
.filter_names_include = filter_names_include, .filter_names_include = filter_names_include,
.filter_names_exclude = filter_names_exclude, .filter_names_exclude = filter_names_exclude,
.nodeNameMode = parse_node_name_mode(node_name_mode_value),
.buildConfig = {}, .buildConfig = {},
.serverConfig = { .serverConfig = {
.enable_server = app.get_option("--server")->as<bool>(), .enable_server = app.get_option("--server")->as<bool>(),

View File

@ -2,6 +2,7 @@
#include "third_party/httplib.h" #include "third_party/httplib.h"
#include "cadit/occt/convert.h" #include "cadit/occt/convert.h"
#include "cadit/occt/debug.h" #include "cadit/occt/debug.h"
#include "cadit/occt/node_naming.h"
#include "http_downloader.h" #include "http_downloader.h"
#include "compression_utils.h" #include "compression_utils.h"
#include <iostream> #include <iostream>
@ -95,6 +96,16 @@ void start_http_server(const GlobalConfig& base_config) {
if (req.form.has_field("maxGeometryNum")) { if (req.form.has_field("maxGeometryNum")) {
config.max_geometry_num = std::stoi(req.form.get_field("maxGeometryNum")); config.max_geometry_num = std::stoi(req.form.get_field("maxGeometryNum"));
} }
if (req.form.has_field("nodeNameMode")) {
try {
config.nodeNameMode = parse_node_name_mode(req.form.get_field("nodeNameMode"));
} catch (const std::exception& ex) {
res.status = 400;
std::string error_msg = "{\"success\":false,\"error\":\"" + std::string(ex.what()) + "\"}";
res.set_content(error_msg, "application/json");
return;
}
}
if (req.form.has_field("compressGlb")) { if (req.form.has_field("compressGlb")) {
config.compress_glb = req.form.get_field("compressGlb") == "true"; config.compress_glb = req.form.get_field("compressGlb") == "true";

View File

@ -13,6 +13,7 @@
#include <chrono> #include <chrono>
#include "cadit/occt/debug.h" #include "cadit/occt/debug.h"
#include "cadit/occt/convert.h" #include "cadit/occt/convert.h"
#include "cadit/occt/node_naming.h"
#include "cadit/occt/bsplinesurf.h" #include "cadit/occt/bsplinesurf.h"
#include "cadit/occt/helpers.h" #include "cadit/occt/helpers.h"
#include "config_utils.h" #include "config_utils.h"
@ -189,6 +190,25 @@ void print_status(const GlobalConfig& config) {
std::cout << "Solid Only: " << config.solidOnly << "\n"; std::cout << "Solid Only: " << config.solidOnly << "\n";
std::cout << "Max Geometry Num: " << config.max_geometry_num << "\n"; std::cout << "Max Geometry Num: " << config.max_geometry_num << "\n";
std::cout << "Tessellation Timeout: " << config.tessellation_timout << "\n\n"; std::cout << "Tessellation Timeout: " << config.tessellation_timout << "\n\n";
std::cout << "Node Name Mode: ";
switch (config.nodeNameMode) {
case NodeNameMode::Source:
std::cout << "source";
break;
case NodeNameMode::Instance:
std::cout << "instance";
break;
case NodeNameMode::Title:
std::cout << "title";
break;
case NodeNameMode::TitleVersion:
std::cout << "title-version";
break;
case NodeNameMode::Combined:
std::cout << "combined";
break;
}
std::cout << "\n\n";
std::cout << "Compression: " << (config.compress_glb ? "enabled" : "disabled") << "\n"; std::cout << "Compression: " << (config.compress_glb ? "enabled" : "disabled") << "\n";
if (config.compress_glb) { if (config.compress_glb) {
@ -241,6 +261,7 @@ int main(int argc, char* argv[])
app.add_option("--filter-names-exclude", "Exclude Filter name. Command separated list")->default_val(""); app.add_option("--filter-names-exclude", "Exclude Filter name. Command separated list")->default_val("");
app.add_option("--filter-names-file-exclude", "Exclude Filter name file")->default_val(""); app.add_option("--filter-names-file-exclude", "Exclude Filter name file")->default_val("");
app.add_option("--tessellation-timeout", "Tessellation timeout")->default_val(30); app.add_option("--tessellation-timeout", "Tessellation timeout")->default_val(30);
app.add_option("--node-name-mode", "GLB node names: source, instance, title, title-version, combined")->default_val("source");
app.add_flag("--compress-glb", "Enable glTF compression using gltfpack"); app.add_flag("--compress-glb", "Enable glTF compression using gltfpack");
app.add_option("--compressed-glb", "Optional output path for compressed GLB (defaults to original)")->default_val(""); app.add_option("--compressed-glb", "Optional output path for compressed GLB (defaults to original)")->default_val("");
app.add_option("--gltfpack-path", "Path to gltfpack executable")->default_val("gltfpack"); app.add_option("--gltfpack-path", "Path to gltfpack executable")->default_val("gltfpack");
@ -259,6 +280,7 @@ int main(int argc, char* argv[])
config.solidOnly = false; config.solidOnly = false;
config.max_geometry_num = 0; config.max_geometry_num = 0;
config.tessellation_timout = app.get_option("--tessellation-timeout")->as<int>(); config.tessellation_timout = app.get_option("--tessellation-timeout")->as<int>();
config.nodeNameMode = parse_node_name_mode(app.get_option("--node-name-mode")->as<std::string>());
config.serverConfig.enable_server = true; config.serverConfig.enable_server = true;
config.serverConfig.port = app.get_option("--port")->as<int>(); config.serverConfig.port = app.get_option("--port")->as<int>();

View File

@ -0,0 +1,31 @@
param(
[Parameter(Mandatory = $true)][string]$ExePath,
[Parameter(Mandatory = $true)][string]$StepPath,
[Parameter(Mandatory = $true)][string]$GlbPath,
[Parameter(Mandatory = $true)][string]$NodeNameMode,
[Parameter(Mandatory = $true)][string]$ExpectedNames
)
$ErrorActionPreference = "Stop"
& $ExePath --stp $StepPath --glb $GlbPath --node-name-mode $NodeNameMode
if ($LASTEXITCODE -ne 0) {
throw "STP2GLB failed with exit code $LASTEXITCODE"
}
$bytes = [System.IO.File]::ReadAllBytes($GlbPath)
if ($bytes.Length -lt 20) {
throw "GLB file is too small"
}
$jsonLength = [System.BitConverter]::ToUInt32($bytes, 12)
$jsonText = [System.Text.Encoding]::UTF8.GetString($bytes, 20, $jsonLength).Trim()
$document = $jsonText | ConvertFrom-Json
$actualNames = @($document.nodes | ForEach-Object { $_.name })
$expectedNameList = @($ExpectedNames -split '\|')
foreach ($expectedName in $expectedNameList) {
if ($actualNames -notcontains $expectedName) {
throw "Missing node name '$expectedName'. Actual names: $($actualNames -join ', ')"
}
}

View File

@ -35,6 +35,30 @@ add_test(NAME debug_as1_mini COMMAND STP2GLB
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin" WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
) )
if(WIN32 AND BUILD_STATIC)
add_test(NAME node_name_title COMMAND powershell
-ExecutionPolicy Bypass
-File ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_node_names.ps1
-ExePath $<TARGET_FILE:STP2GLB>
-StepPath ${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214-mini.stp
-GlbPath ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-node-title-test.glb
-NodeNameMode title
-ExpectedNames "l-bracket-assembly|nut-bolt-assembly|bolt"
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME node_name_combined COMMAND powershell
-ExecutionPolicy Bypass
-File ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_node_names.ps1
-ExePath $<TARGET_FILE:STP2GLB>
-StepPath ${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214-mini.stp
-GlbPath ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-node-combined-test.glb
-NodeNameMode combined
-ExpectedNames "6 (l-bracket-assembly)|2 (nut-bolt-assembly)|1 (bolt)"
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
endif()
add_test(NAME debug_as1_filter COMMAND STP2GLB add_test(NAME debug_as1_filter COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214.stp" --stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-filtered.glb --glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-filtered.glb
@ -54,4 +78,4 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/temp/really_large.stp")
--filter-names-file-exclude=${CMAKE_CURRENT_SOURCE_DIR}/temp/skip-these-nodes.txt --filter-names-file-exclude=${CMAKE_CURRENT_SOURCE_DIR}/temp/skip-these-nodes.txt
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin" WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
) )
endif () endif ()