chore: import existing STP2GLB project

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
sladro 2025-10-22 14:36:07 +08:00
parent 096812b7d2
commit 8218502ee8
86 changed files with 19277 additions and 0 deletions

View File

@ -0,0 +1,18 @@
{
"permissions": {
"allow": [
"mcp__serena__activate_project",
"mcp__serena__check_onboarding_performed",
"mcp__serena__onboarding",
"mcp__serena__list_dir",
"mcp__serena__get_symbols_overview",
"mcp__serena__find_symbol",
"mcp__serena__write_memory",
"mcp__serena__search_for_pattern",
"mcp__context7__resolve-library-id",
"mcp__context7__get-library-docs"
],
"deny": [],
"ask": []
}
}

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# SCM syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true

View File

@ -0,0 +1,104 @@
name: ci-build-and-release
# bump 1
on:
workflow_dispatch:
# push:
# paths:
# - 'src/**'
# - 'cmake/**'
# - '.github/workflows/ci-build-and-release.yml'
permissions:
contents: write
id-token: write
jobs:
build:
runs-on: ${{ matrix.builds.os }}
strategy:
fail-fast: false
matrix:
builds: [
{ name: win, os: windows-latest, exe_path: build/win-static/STP2GLB.exe },
{ name: linux, os: ubuntu-latest, exe_path: build/linux-static/STP2GLB }
]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '1'
- uses: prefix-dev/setup-pixi@v0.8.1
with:
pixi-version: v0.39.2
cache: true
- name: Build
run: |
pixi run -e static build && pixi run -e static install
- name: Upload binary as artifact
uses: actions/upload-artifact@v3
with:
name: STP2GLB-${{ matrix.builds.name }}
path: ${{ matrix.builds.exe_path }}
create-release:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '1'
- name: Get current date
id: date
run: echo "release_date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- run: pip install toml
- name: Read toml using python
shell: python
run: |
import toml
import os
with open('pixi.toml') as f:
data = toml.load(f)
version = data['project']['version']
print(f"{version=}")
with open(os.environ['GITHUB_ENV'], 'a') as fh:
print(f"VERSION={version}", file=fh)
- name: Download Windows binary
uses: actions/download-artifact@v3
with:
name: STP2GLB-win
path: artifacts/windows
- name: Download Linux binary
uses: actions/download-artifact@v3
with:
name: STP2GLB-linux
path: artifacts/linux
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/windows/STP2GLB.exe
artifacts/linux/STP2GLB
tag_name: ${{ env.VERSION }}
name: Release ${{ env.VERSION }}
body: |
The ${{ env.VERSION }} release (${{ env.release_date }}) includes the following binaries:
- Windows: `STP2GLB.exe`
- Linux: `STP2GLB`
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,47 @@
name: ci-occt-static-build
# bump 1
on:
workflow_dispatch:
# push:
# paths:
# - 'recipes/occt-static/**'
# - '.github/workflows/ci-occt-static-build.yml'
permissions:
contents: read
id-token: write
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '1'
- uses: prefix-dev/setup-pixi@v0.8.1
with:
pixi-version: v0.40.2
activate-environment: conda
cache: true
- name: Build
run: |
pixi run -e conda build-occt
- name: Upload all packages
shell: pixi run bash -e {0}
run: |
shopt -s nullglob
EXIT_CODE=0
for pkg in $(find output -type f \( -name "*.conda" -o -name "*.tar.bz2" \) ); do
if ! rattler-build upload prefix -c adapy-tools "${pkg}"; then
EXIT_CODE=1
fi
done
exit $EXIT_CODE

View File

@ -0,0 +1,47 @@
name: ci-stepcode-static-build
# bump 1
on:
workflow_dispatch:
# push:
# paths:
# - 'recipes/stepcode-static/**'
# - '.github/workflows/ci-stepcode-static-build.yml'
permissions:
contents: read
id-token: write
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '1'
- uses: prefix-dev/setup-pixi@v0.8.1
with:
pixi-version: v0.40.2
activate-environment: conda
cache: true
- name: Build
run: |
pixi run -e conda build-stepcode
- name: Upload all packages
shell: pixi run bash -e {0}
run: |
shopt -s nullglob
EXIT_CODE=0
for pkg in $(find output -type f \( -name "*.conda" -o -name "*.tar.bz2" \) ); do
if ! rattler-build upload prefix -c adapy-tools "${pkg}"; then
EXIT_CODE=1
fi
done
exit $EXIT_CODE

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
build/
.idea/
cmake-build*/
.env
.env.json
temp/
.vs/
# pixi environments
.pixi/
output/

Binary file not shown.

View File

@ -0,0 +1,46 @@
# 代码风格和约定
## C++代码风格
### 命名约定
- **变量名**: 小写+下划线 (snake_case)
- `linear_deflection`, `debug_mode`, `max_geometry_num`
- **函数名**: 小写+下划线 (snake_case)
- `convert_stp_to_glb()`, `print_status()`, `process_parameters()`
- **类名**: 大写驼峰 (PascalCase)
- `GlobalConfig`, `BuildConfig`
- **结构体成员**: 小写+下划线
- `stpFile`, `glbFile`, `buildConfig`
- **常量**: 大写+下划线
- **宏定义**: 大写+下划线
### 代码组织
- **头文件保护**: 使用 `#pragma once` 或传统的头文件保护
- **包含顺序**:
1. 系统头文件
2. 第三方库头文件
3. 项目头文件
- **命名空间**: 避免使用 `using namespace std` 在头文件中
### 函数和类设计
- **函数长度**: 保持函数简洁主函数约60行
- **错误处理**: 使用异常处理机制
- **RAII原则**: 资源获取即初始化
- **const正确性**: 适当使用const关键字
### 项目特定约定
- **配置结构**: 使用结构体组织配置参数
- **CLI参数**: 使用CLI11库进行命令行解析
- **错误输出**: 使用 `std::cerr` 输出错误信息
- **时间测量**: 使用 `std::chrono` 进行性能测量
### Windows特定
- **宏定义**:
- `WIN32_LEAN_AND_MEAN`
- `NOMINMAX`
- **运行时**: 静态构建使用 `/MT` 标志
### 文件组织
- 头文件和实现文件分离
- 每个模块有独立的目录结构
- 测试文件单独组织在tests目录

View File

@ -0,0 +1,35 @@
# 代码库结构
## 目录结构
```
Stp2Glb/
├── src/ # 源代码目录
│ ├── main.cpp # 主程序入口
│ ├── config_utils.cpp # 配置处理工具
│ ├── config_structs.h # 配置数据结构
│ ├── cadit/ # CAD处理模块
│ │ ├── occt/ # OpenCASCADE相关代码
│ │ ├── ifc/ # IFC文件处理
│ │ ├── stepcode/ # STEP文件解析
│ │ └── tinygltf/ # GLTF/GLB输出
│ └── geom/ # 几何数据结构
├── tests/ # 测试配置
├── cmake/ # CMake配置文件
├── files/ # 测试文件
├── recipes/ # Conda构建配方
└── CMakeLists.txt # 主CMake配置
```
## 核心模块
- **main.cpp**: 程序入口,命令行解析和主流程控制
- **cadit/occt/**: OpenCASCADE集成STEP文件读取和几何处理
- **cadit/occt/convert.cpp**: 主要转换逻辑
- **cadit/occt/gltf_writer.cpp**: GLB文件写入
- **cadit/occt/step_tree.cpp**: STEP文件树结构处理
- **geom/**: 几何数据结构定义
## 关键文件
- **CMakeLists.txt**: 主构建配置
- **pixi.toml**: 依赖管理和任务定义
- **CMakePresets.json**: CMake预设配置
- **tests/tests.cmake**: 测试用例定义

View File

@ -0,0 +1,55 @@
# 开发指南和最佳实践
## 开发原则
1. **渐进迭代**: 小步快跑,每次提交保持稳定
2. **稳定优先**: 不破坏现有功能
3. **学习复用**: 研究现有代码模式,保持一致性
4. **架构先行**: 设计后实现,确保可维护性
## 技术栈理解
- **Core**: C++20, CMake, Pixi包管理
- **CAD处理**: OpenCASCADE (OCCT) 7.8.1
- **几何算法**: CGAL 5.6.1
- **文件格式**: STEP输入, GLB输出
- **并行处理**: TBB库
- **JSON**: nlohmann_json
- **CLI**: CLI11库
## 开发工作流
1. **环境准备**: `pixi install` && `pixi shell`
2. **理解需求**: 分析要实现的功能
3. **研究现有代码**: 查看相似功能的实现
4. **小步实现**: 增量开发,频繁测试
5. **质量验证**: 编译、测试、手动验证
6. **文档更新**: 必要时更新README和注释
## 架构约束
- **不修改现有接口**: 除非绝对必要
- **向后兼容**: 新功能不能破坏现有功能
- **模块化**: 保持清晰的模块边界
- **错误处理**: 使用C++异常机制
## 性能考虑
- **大文件处理**: 注意内存使用
- **并行处理**: 合理使用TBB
- **几何计算**: 优化OCCT和CGAL的使用
- **I/O操作**: 高效的文件读写
## 调试技巧
- **使用调试模式**: `--debug` 参数获取详细信息
- **分步测试**: 从小文件开始测试
- **性能分析**: 使用计时器测量关键操作
- **内存检查**: 注意OpenCASCADE对象的生命周期
## 常见陷阱
- **路径处理**: Windows路径分隔符问题
- **字符编码**: 文件名和路径的编码处理
- **内存管理**: OCCT对象的智能指针使用
- **线程安全**: 并行处理时的数据竞争
## 代码审查要点
- 是否遵循现有命名约定
- 错误处理是否完整
- 是否有潜在的内存泄漏
- 性能是否符合预期
- 测试覆盖是否充分

View File

@ -0,0 +1,35 @@
# STP2GLB 项目概览
## 项目目的
STP2GLB 是一个STEP文件到GLB(glTF二进制)格式的转换器。它是一个命令行工具能够将CAD格式的STEP文件转换为适用于Web和3D可视化的GLB格式。
## 主要功能
- STEP文件到GLB格式转换
- 支持线性和角度偏转参数调整
- 调试模式,提供更详细的转换信息
- 支持实体几何过滤
- 支持名称过滤(包含/排除特定几何体)
- 可配置的镶嵌超时设置
- 支持最大几何体数量限制
## 技术架构
- **语言**: C++20
- **构建系统**: CMake 3.20+
- **包管理**: Pixi (conda生态)
- **编译器**: MSVC (Windows), GCC (Linux)
- **平台**: Windows 64-bit, Linux 64-bit
## 主要依赖库
- **OpenCASCADE (OCCT) 7.8.1**: CAD几何处理核心库
- **CLI11**: 命令行参数解析
- **nlohmann_json**: JSON处理
- **tinygltf 2.8.19**: GLTF/GLB文件处理
- **CGAL 5.6.1**: 计算几何算法库
- **TBB**: 并行计算库
- **IFCOpenShell**: IFC文件支持
- **StepCode 0.8.2**: STEP文件解析
## 构建模式
- **动态链接**: 用于开发和调试
- **静态链接**: 用于发布部署
- **调试模式**: 包含调试符号的动态构建

View File

@ -0,0 +1,81 @@
# 建议的开发命令
## 环境设置
```bash
# 安装依赖环境
pixi install
# 激活环境
pixi shell
```
## 构建命令
### 动态构建 (开发推荐)
```bash
# Windows构建
pixi run build
pixi run install
# 调试构建
pixi run -e dynamic-debug build
pixi run -e dynamic-debug install
```
### 静态构建 (发布用)
```bash
# 静态构建
pixi run -e static build
pixi run -e static install
```
## 测试命令
```bash
# 运行测试 (动态构建)
pixi run test
# 运行测试 (调试构建)
pixi run -e dynamic-debug test
# 手动运行CTest
ctest --test-dir build/win-dynamic-release
```
## 手动执行
```bash
# 基本转换
STP2GLB.exe --stp input.stp --glb output.glb
# 调试模式转换
STP2GLB.exe --stp input.stp --glb output.glb --debug
# 高级选项
STP2GLB.exe --stp input.stp --glb output.glb --lin-defl 0.1 --ang-defl 0.5 --solid-only
```
## Windows特定工具
```bash
# 查看文件
dir
type filename.txt
# 搜索文件
findstr "pattern" *.cpp
# 文件操作
copy source.txt dest.txt
move source.txt dest.txt
del filename.txt
```
## 构建环境检查
```bash
# 检查Visual Studio工具
vswhere -latest -property installationPath
# 检查CMake版本
cmake --version
# 检查Ninja
ninja --version
```

View File

@ -0,0 +1,65 @@
# 任务完成检查清单
## 代码完成后必须执行的步骤
### 1. 构建验证
```bash
# 确保代码能够成功编译
pixi run build
# 检查是否有编译警告或错误
# 所有警告都应该被解决
```
### 2. 测试验证
```bash
# 运行所有测试用例
pixi run test
# 确保所有测试通过
# 新功能必须有相应的测试覆盖
```
### 3. 代码质量检查
- **无编译警告**: 代码应该零警告编译
- **异常处理**: 适当的错误处理和异常捕获
- **内存管理**: 无内存泄漏遵循RAII原则
- **const正确性**: 适当使用const关键字
### 4. 功能验证
```bash
# 手动测试核心功能
STP2GLB.exe --stp test.stp --glb test.glb
# 验证输出文件的正确性
# 检查GLB文件是否可以正常打开和渲染
```
### 5. 文档更新
- 如果修改了CLI参数更新README.md
- 如果添加了新功能,更新相关文档
- 确保代码注释的准确性
### 6. 性能检查
- 对于性能敏感的修改,运行性能测试
- 确保没有引入明显的性能回归
### 7. Windows特定检查
- 验证MSVC编译器兼容性
- 检查静态链接构建是否正常
- 确保路径分隔符正确处理
## 提交前检查
- [ ] 代码编译成功
- [ ] 所有测试通过
- [ ] 无编译警告
- [ ] 功能验证完成
- [ ] 文档已更新
- [ ] 代码风格一致
## 不允许的操作
- ❌ 提交不能编译的代码
- ❌ 跳过测试验证
- ❌ 忽略编译警告
- ❌ 破坏现有功能
- ❌ 修改核心架构而不征得同意

68
.serena/project.yml Normal file
View File

@ -0,0 +1,68 @@
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
# * For C, use cpp
# * For JavaScript, use typescript
# Special requirements:
# * csharp: Requires the presence of a .sln file in the project folder.
language: cpp
# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true
# list of additional paths to ignore
# same syntax as gitignore, so you can use * and **
# Was previously called `ignored_dirs`, please update your config if you are using that.
# Added (renamed)on 2025-04-07
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
project_name: "Stp2Glb"

96
CLAUDE.md Normal file
View File

@ -0,0 +1,96 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
STP2GLB is a C++20 command-line tool that converts STEP (CAD) files to GLB (glTF binary) format for 3D visualization. The core conversion pipeline uses OpenCASCADE Technology (OCCT) for CAD geometry processing and outputs to GLB via tinygltf.
## Build System and Commands
This project uses Pixi for package management and CMake with presets for building. All development should occur within Pixi environments.
### Essential Commands
```bash
# Setup environment
pixi install
pixi shell
# Development builds (with debugging symbols)
pixi run build # Windows dynamic release
pixi run install # Install after build
pixi run -e dynamic-debug build # Debug build with symbols
pixi run -e dynamic-debug install
# Testing
pixi run test # Run all tests
pixi run -e dynamic-debug test # Run tests with debug build
ctest --test-dir build/win-dynamic-release # Direct CTest execution
# Production builds (static linking)
pixi run -e static build
pixi run -e static install
```
### Manual Testing
```bash
# Basic conversion
STP2GLB.exe --stp input.stp --glb output.glb
# Debug mode (provides detailed STEP entity failure information)
STP2GLB.exe --stp input.stp --glb output.glb --debug
# Advanced options
STP2GLB.exe --stp input.stp --glb output.glb --lin-defl 0.1 --ang-defl 0.5 --solid-only --max-geometry-num 100
```
## Architecture Overview
### Core Data Flow
1. **CLI Parsing** (`main.cpp`) → **Configuration** (`config_structs.h`, `config_utils.cpp`)
2. **STEP Reading** (`cadit/occt/step_helpers.cpp`) → **Geometry Processing** (`cadit/occt/convert.cpp`)
3. **Tessellation & Filtering****GLB Output** (`cadit/occt/gltf_writer.cpp`)
### Key Modules
- **`src/cadit/occt/`**: OpenCASCADE integration layer
- `convert.cpp`: Main conversion pipeline (`convert_stp_to_glb()`, `debug_stp_to_glb()`)
- `step_tree.cpp`: STEP file hierarchical processing
- `gltf_writer.cpp`: GLB format output generation
- `step_helpers.cpp`: STEP entity manipulation utilities
- `helpers.cpp`: OCCT geometry utilities
- **`src/geom/`**: Abstract geometry data structures
- Mesh, Color, and geometric primitives
- Bridge between OCCT and output formats
### Build Configurations
The project supports multiple build modes via CMake presets:
- **Dynamic builds**: For development (faster builds, shared libraries)
- **Static builds**: For distribution (single executable, slower builds)
- **Debug vs Release**: Debug includes detailed STEP entity failure reporting
### Dependencies Architecture
- **OpenCASCADE 7.8.1**: CAD kernel for STEP reading and geometry processing
- **CGAL 5.6.1**: Computational geometry algorithms
- **CLI11**: Command-line argument parsing
- **tinygltf 2.8.19**: GLB/glTF file format handling
- **nlohmann_json**: JSON processing for glTF structure
## Development Patterns
### Error Handling Strategy
The codebase uses two conversion modes:
- **Standard mode**: Fast conversion with basic error handling
- **Debug mode** (`--debug` flag): Slower but provides detailed reporting of which STEP entities failed to convert and why
### Configuration Management
All runtime parameters flow through `GlobalConfig` struct, populated via CLI11 from command-line arguments. This centralized configuration pattern should be maintained for new features.
### Testing Strategy
Tests are defined in `tests/tests.cmake` and use actual STEP files from `files/` directory. New features should include corresponding test cases with real geometry files when possible.
### Code Style
- Snake_case for functions and variables (`convert_stp_to_glb`, `linear_deflection`)
- PascalCase for classes/structs (`GlobalConfig`, `BuildConfig`)
- Use RAII and C++20 features appropriately
- Prefer exceptions for error handling in conversion pipeline

121
CMakePresets.json Normal file
View File

@ -0,0 +1,121 @@
{
"version": 6,
"configurePresets": [
{
"name": "win-shared",
"generator": "Ninja",
"hidden": true,
"cacheVariables": {
"CLI11_DIR": "$env{LIBRARY_PREFIX}/include",
"CMAKE_PREFIX_PATH": "$env{LIBRARY_PREFIX};$env{LIBRARY_PREFIX}/include;$env{LIBRARY_PREFIX}/lib;$env{LIBRARY_PREFIX}/bin",
"CMAKE_INSTALL_PREFIX": "$env{LIBRARY_PREFIX}",
"OpenCASCADE_DIR": "$env{LIBRARY_PREFIX}/lib/cmake/opencascade",
"OpenCASCADE_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include/opencascade",
"CONDA_LOCAL_DEV": "ON"
}
},
{
"name": "linux-shared",
"generator": "Ninja",
"hidden": true,
"cacheVariables": {
"CLI11_DIR": "$env{PREFIX}/include",
"CMAKE_PREFIX_PATH": "$env{PREFIX}:$env{PREFIX}/include:$env{PREFIX}/lib:$env{PREFIX}/bin",
"CMAKE_INSTALL_PREFIX": "$env{PREFIX}",
"OpenCASCADE_DIR": "$env{PREFIX}/lib/cmake/opencascade",
"OpenCASCADE_INCLUDE_DIR": "$env{PREFIX}/include/opencascade"
}
},
{
"name": "win-dynamic-debug",
"inherits": "win-shared",
"hidden": false,
"binaryDir": "${sourceDir}/build/${presetName}",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/dynamic-debug",
"LIBRARY_PREFIX": "${sourceDir}/.pixi/envs/dynamic-debug/Library"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTING": "ON"
}
},
{
"name": "win-dynamic-release",
"hidden": false,
"inherits": "win-shared",
"binaryDir": "${sourceDir}/build/${presetName}",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/dynamic",
"LIBRARY_PREFIX": "${sourceDir}/.pixi/envs/dynamic/Library"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTING": "ON",
"CGAL_DIR": "$env{LIBRARY_PREFIX}/lib/cmake/CGAL"
}
},
{
"name": "win-static",
"inherits": "win-shared",
"hidden": false,
"binaryDir": "${sourceDir}/build/${presetName}",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/static",
"LIBRARY_PREFIX": "${sourceDir}/.pixi/envs/static/Library"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTING": "OFF",
"BUILD_STATIC": "ON",
"BUILD_SHARED_LIBS": "OFF",
"CONDA_LOCAL_DEV": "OFF"
}
},
{
"name": "win-static-mingw",
"inherits": "win-shared",
"binaryDir": "${sourceDir}/build/${presetName}",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/static-mingw",
"LIBRARY_PREFIX": "${sourceDir}/.pixi/envs/static-mingw/Library",
"CXX": "x86_64-w64-mingw32-g++"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTING": "OFF",
"BUILD_STATIC": "ON",
"BUILD_SHARED_LIBS": "OFF",
"CONDA_LOCAL_DEV": "OFF"
}
},
{
"name": "linux-static",
"binaryDir": "${sourceDir}/build/${presetName}",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/static"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXE_LINKER_FLAGS": "-std=c++11 -fuse-ld=lld -lpthread -ldl -lm",
"BUILD_TESTING": "OFF",
"BUILD_STATIC": "ON",
"CONDA_LOCAL_DEV": "ON"
}
},
{
"name": "linux-dynamic",
"binaryDir": "${sourceDir}/build/${presetName}",
"inherits": "linux-shared",
"environment": {
"PREFIX": "${sourceDir}/.pixi/envs/dynamic"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTING": "ON",
"BUILD_STATIC": "OFF",
"CONDA_LOCAL_DEV": "ON"
}
}
]
}

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# Stp2Glb
## Usage
```bash
STEP to GLB converter
Usage: STP2GLB.exe [OPTIONS]
Options:
-h,--help Print this help message and exit
--stp REQUIRED STEP filepath
--glb REQUIRED GLB filepath
--lin-defl :FLOAT in [0 - 1] [0.1]
Linear deflection
--ang-defl :FLOAT in [0 - 1] [0.5]
Angular deflection
--rel-defl Relative deflection
--debug Debug mode. More robust but slower
--solid-only Solid only
--max-geometry-num [0] Maximum number of geometries to convert
--filter-names-include Include Filter name. Command separated list
--filter-names-file-include Include Filter name file
--filter-names-exclude Exclude Filter name. Command separated list
--filter-names-file-exclude Exclude Filter name file
--tessellation-timeout [30]
Tessellation timeout
```
## Development
### Install Pre-requisites
The pre-requisites build requirements are conda packages handled by using [pixi](https://pixi.sh).
Note that on windows the MSVC c++ compiler toolchain is used.
So a pre-requisite on windows is that you have installed VS or VS build tools from https://visualstudio.microsoft.com/downloads/?q=build+tools
### Building
To build the STP2GLB executable using shared dependencies, run the following command:
```bash
pixi run build && pixi run install
```
To build the STP2GLB executable using static dependencies, run the following command:
```bash
pixi run -e static build && pixi run -e static install
```
### Local IDE development
You can use the presets in the CMakePresets.json file (which points to the pixi environment). Just make sure you've
either installed the pixi environment using `pixi install` or have run any of the build commands above.
## Performance metrics
Todo

BIN
STP2GLB.exe Normal file

Binary file not shown.

2
aa-log.json Normal file
View File

@ -0,0 +1,2 @@
[
]

BIN
aa.glb Normal file

Binary file not shown.

12
cmake/deps_cgal.cmake Normal file
View File

@ -0,0 +1,12 @@
# Find CGAL
find_package(CGAL REQUIRED)
if (CGAL_FOUND)
message(STATUS "CGAL version found: " ${CGAL_VERSION})
message(STATUS "CGAL include directory: " ${CGAL_INCLUDE_DIRS})
message(STATUS "CGAL binary directory: " ${CGAL_DIR})
message(STATUS "CGAL library directory: " ${CGAL_LIBRARY_DIRS})
include_directories(${CGAL_INCLUDE_DIRS})
link_directories(${CGAL_LIBRARY_DIRS})
endif (CGAL_FOUND)

8
cmake/deps_ifc.cmake Normal file
View File

@ -0,0 +1,8 @@
# link the library files located in %LIBRARY_PREFIX%/lib/ifcparse/IfcParse.lib (on windows as an example)
# to the executable
list(APPEND
ADA_CPP_LINK_LIBS
IfcParse
IfcGeom
)

17
cmake/deps_occ.cmake Normal file
View File

@ -0,0 +1,17 @@
# Find OpenCASCADE
find_package(OpenCASCADE REQUIRED)
if (OpenCASCADE_FOUND)
message(STATUS "OpenCASCADE version found: " ${OpenCASCADE_MAJOR_VERSION} ".." ${OpenCASCADE_MINOR_VERSION} ".." ${OpenCASCADE_MAINTENANCE_VERSION})
message(STATUS "OpenCASCADE include directory: " ${OpenCASCADE_INCLUDE_DIR})
message(STATUS "OpenCASCADE binary directory: " ${OpenCASCADE_BINARY_DIR})
message(STATUS "OpenCASCADE library directory: " ${OpenCASCADE_LIBRARY_DIR})
include_directories(${OpenCASCADE_INCLUDE_DIR})
link_directories(${OpenCASCADE_LIBRARY_DIR})
# Order of linking is important
# https://dev.opencascade.org/node/71506#comment-847
list(APPEND
ADA_CPP_LINK_LIBS
${OpenCASCADE_LIBRARIES}
)
endif (OpenCASCADE_FOUND)

10
cmake/deps_stepcode.cmake Normal file
View File

@ -0,0 +1,10 @@
include_directories(${STEPCODE_INCLUDE_DIR})
list(APPEND
ADA_CPP_LINK_LIBS
steplazyfile
stepdai
stepcore
stepeditor
steputils
)

6
cmake/pre_checks.cmake Normal file
View File

@ -0,0 +1,6 @@
if (NOT CMAKE_BUILD_TYPE)
message(STATUS "Build type not set, defaulting to Release")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif ()
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})

BIN
dragon_drc.glb Normal file

Binary file not shown.

1409
files/as1-oc-214-mini.stp Normal file

File diff suppressed because it is too large Load Diff

8378
files/as1-oc-214.stp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,286 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('FreeCAD Model'),'2;1');
FILE_NAME('Open CASCADE Shape Model','2023-04-13T14:35:14',(''),(''),
'Open CASCADE STEP processor 7.6','FreeCAD','Unknown');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
ENDSEC;
DATA;
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2000,#2);
#2 = APPLICATION_CONTEXT(
'core data for automotive mechanical design processes');
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
#5 = PRODUCT_DEFINITION('design','',#6,#9);
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
#7 = PRODUCT('Unnamed2','Unnamed2','',(#8));
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#10 = SHAPE_REPRESENTATION('',(#11,#15,#19),#23);
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
#12 = CARTESIAN_POINT('',(0.,0.,0.));
#13 = DIRECTION('',(0.,0.,1.));
#14 = DIRECTION('',(1.,0.,-0.));
#15 = AXIS2_PLACEMENT_3D('',#16,#17,#18);
#16 = CARTESIAN_POINT('',(0.,0.,4.504));
#17 = DIRECTION('',(0.,0.,1.));
#18 = DIRECTION('',(1.,0.,0.));
#19 = AXIS2_PLACEMENT_3D('',#20,#21,#22);
#20 = CARTESIAN_POINT('',(0.,0.,0.));
#21 = DIRECTION('',(0.,0.,1.));
#22 = DIRECTION('',(1.,0.,0.));
#23 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#27)) GLOBAL_UNIT_ASSIGNED_CONTEXT(
(#24,#25,#26)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#24 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.) );
#25 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#26 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#27 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-10),#24,
'distance_accuracy_value','confusion accuracy');
#28 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
#29 = SHAPE_DEFINITION_REPRESENTATION(#30,#36);
#30 = PRODUCT_DEFINITION_SHAPE('','',#31);
#31 = PRODUCT_DEFINITION('design','',#32,#35);
#32 = PRODUCT_DEFINITION_FORMATION('','',#33);
#33 = PRODUCT('red_plate','red_plate','',(#34));
#34 = PRODUCT_CONTEXT('',#2,'mechanical');
#35 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#36 = MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#11,#37),#111);
#37 = SHELL_BASED_SURFACE_MODEL('',(#38));
#38 = OPEN_SHELL('',(#39));
#39 = ADVANCED_FACE('',(#40),#54,.T.);
#40 = FACE_BOUND('',#41,.T.);
#41 = EDGE_LOOP('',(#42,#65,#81,#97));
#42 = ORIENTED_EDGE('',*,*,#43,.T.);
#43 = EDGE_CURVE('',#44,#46,#48,.T.);
#44 = VERTEX_POINT('',#45);
#45 = CARTESIAN_POINT('',(10.,0.,0.));
#46 = VERTEX_POINT('',#47);
#47 = CARTESIAN_POINT('',(10.,10.,0.));
#48 = SURFACE_CURVE('',#49,(#53),.PCURVE_S1.);
#49 = LINE('',#50,#51);
#50 = CARTESIAN_POINT('',(10.,0.,0.));
#51 = VECTOR('',#52,1.E-03);
#52 = DIRECTION('',(0.,1.,0.));
#53 = PCURVE('',#54,#59);
#54 = PLANE('',#55);
#55 = AXIS2_PLACEMENT_3D('',#56,#57,#58);
#56 = CARTESIAN_POINT('',(5.,5.,0.));
#57 = DIRECTION('',(0.,0.,1.));
#58 = DIRECTION('',(1.,0.,-0.));
#59 = DEFINITIONAL_REPRESENTATION('',(#60),#64);
#60 = LINE('',#61,#62);
#61 = CARTESIAN_POINT('',(5.,-5.));
#62 = VECTOR('',#63,1.);
#63 = DIRECTION('',(0.,1.));
#64 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#65 = ORIENTED_EDGE('',*,*,#66,.T.);
#66 = EDGE_CURVE('',#46,#67,#69,.T.);
#67 = VERTEX_POINT('',#68);
#68 = CARTESIAN_POINT('',(0.,10.,0.));
#69 = SURFACE_CURVE('',#70,(#74),.PCURVE_S1.);
#70 = LINE('',#71,#72);
#71 = CARTESIAN_POINT('',(10.,10.,0.));
#72 = VECTOR('',#73,1.E-03);
#73 = DIRECTION('',(-1.,0.,0.));
#74 = PCURVE('',#54,#75);
#75 = DEFINITIONAL_REPRESENTATION('',(#76),#80);
#76 = LINE('',#77,#78);
#77 = CARTESIAN_POINT('',(5.,5.));
#78 = VECTOR('',#79,1.);
#79 = DIRECTION('',(-1.,0.));
#80 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#81 = ORIENTED_EDGE('',*,*,#82,.T.);
#82 = EDGE_CURVE('',#67,#83,#85,.T.);
#83 = VERTEX_POINT('',#84);
#84 = CARTESIAN_POINT('',(0.,0.,0.));
#85 = SURFACE_CURVE('',#86,(#90),.PCURVE_S1.);
#86 = LINE('',#87,#88);
#87 = CARTESIAN_POINT('',(0.,10.,0.));
#88 = VECTOR('',#89,1.E-03);
#89 = DIRECTION('',(0.,-1.,0.));
#90 = PCURVE('',#54,#91);
#91 = DEFINITIONAL_REPRESENTATION('',(#92),#96);
#92 = LINE('',#93,#94);
#93 = CARTESIAN_POINT('',(-5.,5.));
#94 = VECTOR('',#95,1.);
#95 = DIRECTION('',(0.,-1.));
#96 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#97 = ORIENTED_EDGE('',*,*,#98,.T.);
#98 = EDGE_CURVE('',#83,#44,#99,.T.);
#99 = SURFACE_CURVE('',#100,(#104),.PCURVE_S1.);
#100 = LINE('',#101,#102);
#101 = CARTESIAN_POINT('',(0.,0.,0.));
#102 = VECTOR('',#103,1.E-03);
#103 = DIRECTION('',(1.,0.,0.));
#104 = PCURVE('',#54,#105);
#105 = DEFINITIONAL_REPRESENTATION('',(#106),#110);
#106 = LINE('',#107,#108);
#107 = CARTESIAN_POINT('',(-5.,-5.));
#108 = VECTOR('',#109,1.);
#109 = DIRECTION('',(1.,0.));
#110 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#111 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#115)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#112,#113,#114)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#112 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.) );
#113 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#114 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#115 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-10),#112,
'distance_accuracy_value','confusion accuracy');
#116 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#117,#119);
#117 = ( REPRESENTATION_RELATIONSHIP('','',#36,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#118)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#118 = ITEM_DEFINED_TRANSFORMATION('','',#11,#15);
#119 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',#120
);
#120 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('3','red_plate','',#5,#31,$);
#121 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#33));
#122 = SHAPE_DEFINITION_REPRESENTATION(#123,#129);
#123 = PRODUCT_DEFINITION_SHAPE('','',#124);
#124 = PRODUCT_DEFINITION('design','',#125,#128);
#125 = PRODUCT_DEFINITION_FORMATION('','',#126);
#126 = PRODUCT('blue_plate','blue_plate','',(#127));
#127 = PRODUCT_CONTEXT('',#2,'mechanical');
#128 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#129 = MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#11,#130),#204);
#130 = SHELL_BASED_SURFACE_MODEL('',(#131));
#131 = OPEN_SHELL('',(#132));
#132 = ADVANCED_FACE('',(#133),#147,.T.);
#133 = FACE_BOUND('',#134,.T.);
#134 = EDGE_LOOP('',(#135,#158,#174,#190));
#135 = ORIENTED_EDGE('',*,*,#136,.T.);
#136 = EDGE_CURVE('',#137,#139,#141,.T.);
#137 = VERTEX_POINT('',#138);
#138 = CARTESIAN_POINT('',(10.,0.,0.));
#139 = VERTEX_POINT('',#140);
#140 = CARTESIAN_POINT('',(10.,10.,0.));
#141 = SURFACE_CURVE('',#142,(#146),.PCURVE_S1.);
#142 = LINE('',#143,#144);
#143 = CARTESIAN_POINT('',(10.,0.,0.));
#144 = VECTOR('',#145,1.E-03);
#145 = DIRECTION('',(0.,1.,0.));
#146 = PCURVE('',#147,#152);
#147 = PLANE('',#148);
#148 = AXIS2_PLACEMENT_3D('',#149,#150,#151);
#149 = CARTESIAN_POINT('',(5.,5.,0.));
#150 = DIRECTION('',(0.,0.,1.));
#151 = DIRECTION('',(1.,0.,-0.));
#152 = DEFINITIONAL_REPRESENTATION('',(#153),#157);
#153 = LINE('',#154,#155);
#154 = CARTESIAN_POINT('',(5.,-5.));
#155 = VECTOR('',#156,1.);
#156 = DIRECTION('',(0.,1.));
#157 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#158 = ORIENTED_EDGE('',*,*,#159,.T.);
#159 = EDGE_CURVE('',#139,#160,#162,.T.);
#160 = VERTEX_POINT('',#161);
#161 = CARTESIAN_POINT('',(0.,10.,0.));
#162 = SURFACE_CURVE('',#163,(#167),.PCURVE_S1.);
#163 = LINE('',#164,#165);
#164 = CARTESIAN_POINT('',(10.,10.,0.));
#165 = VECTOR('',#166,1.E-03);
#166 = DIRECTION('',(-1.,0.,0.));
#167 = PCURVE('',#147,#168);
#168 = DEFINITIONAL_REPRESENTATION('',(#169),#173);
#169 = LINE('',#170,#171);
#170 = CARTESIAN_POINT('',(5.,5.));
#171 = VECTOR('',#172,1.);
#172 = DIRECTION('',(-1.,0.));
#173 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#174 = ORIENTED_EDGE('',*,*,#175,.T.);
#175 = EDGE_CURVE('',#160,#176,#178,.T.);
#176 = VERTEX_POINT('',#177);
#177 = CARTESIAN_POINT('',(0.,0.,0.));
#178 = SURFACE_CURVE('',#179,(#183),.PCURVE_S1.);
#179 = LINE('',#180,#181);
#180 = CARTESIAN_POINT('',(0.,10.,0.));
#181 = VECTOR('',#182,1.E-03);
#182 = DIRECTION('',(0.,-1.,0.));
#183 = PCURVE('',#147,#184);
#184 = DEFINITIONAL_REPRESENTATION('',(#185),#189);
#185 = LINE('',#186,#187);
#186 = CARTESIAN_POINT('',(-5.,5.));
#187 = VECTOR('',#188,1.);
#188 = DIRECTION('',(0.,-1.));
#189 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#190 = ORIENTED_EDGE('',*,*,#191,.T.);
#191 = EDGE_CURVE('',#176,#137,#192,.T.);
#192 = SURFACE_CURVE('',#193,(#197),.PCURVE_S1.);
#193 = LINE('',#194,#195);
#194 = CARTESIAN_POINT('',(0.,0.,0.));
#195 = VECTOR('',#196,1.E-03);
#196 = DIRECTION('',(1.,0.,0.));
#197 = PCURVE('',#147,#198);
#198 = DEFINITIONAL_REPRESENTATION('',(#199),#203);
#199 = LINE('',#200,#201);
#200 = CARTESIAN_POINT('',(-5.,-5.));
#201 = VECTOR('',#202,1.);
#202 = DIRECTION('',(1.,0.));
#203 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#204 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#208)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#205,#206,#207)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#205 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.) );
#206 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#207 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#208 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-10),#205,
'distance_accuracy_value','confusion accuracy');
#209 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#210,#212);
#210 = ( REPRESENTATION_RELATIONSHIP('','',#129,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#211)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#211 = ITEM_DEFINED_TRANSFORMATION('','',#11,#19);
#212 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',#213
);
#213 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('4','blue_plate','',#5,#124,$);
#214 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#126));
#215 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#216)
,#111);
#216 = STYLED_ITEM('color',(#217),#37);
#217 = PRESENTATION_STYLE_ASSIGNMENT((#218,#224));
#218 = SURFACE_STYLE_USAGE(.BOTH.,#219);
#219 = SURFACE_SIDE_STYLE('',(#220));
#220 = SURFACE_STYLE_FILL_AREA(#221);
#221 = FILL_AREA_STYLE('',(#222));
#222 = FILL_AREA_STYLE_COLOUR('',#223);
#223 = DRAUGHTING_PRE_DEFINED_COLOUR('red');
#224 = CURVE_STYLE('',#225,POSITIVE_LENGTH_MEASURE(0.1),#226);
#225 = DRAUGHTING_PRE_DEFINED_CURVE_FONT('continuous');
#226 = COLOUR_RGB('',9.803921802644E-02,9.803921802644E-02,
9.803921802644E-02);
#227 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#228)
,#204);
#228 = STYLED_ITEM('color',(#229),#130);
#229 = PRESENTATION_STYLE_ASSIGNMENT((#230,#236));
#230 = SURFACE_STYLE_USAGE(.BOTH.,#231);
#231 = SURFACE_SIDE_STYLE('',(#232));
#232 = SURFACE_STYLE_FILL_AREA(#233);
#233 = FILL_AREA_STYLE('',(#234));
#234 = FILL_AREA_STYLE_COLOUR('',#235);
#235 = DRAUGHTING_PRE_DEFINED_COLOUR('blue');
#236 = CURVE_STYLE('',#237,POSITIVE_LENGTH_MEASURE(0.1),#226);
#237 = DRAUGHTING_PRE_DEFINED_CURVE_FONT('continuous');
ENDSEC;
END-ISO-10303-21;

303
files/my_test.ifc Normal file
View File

@ -0,0 +1,303 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2023-09-18T20:15:51+02:00',(),(),'IfcOpenShell 0.7.0','IfcOpenShell 0.7.0','Nobody');
FILE_SCHEMA(('IFC4X1'));
ENDSEC;
DATA;
#1=IFCPROJECT('1FHXGqcyrEhvZl5Q2VSoJh',$,'AdaProject',$,$,$,$,(#10),#5);
#2=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#3=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#4=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#5=IFCUNITASSIGNMENT((#2,#3,#4));
#6=IFCCARTESIANPOINT((0.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCDIRECTION((1.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#6,#7,#8);
#10=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,$);
#11=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#10,$,.MODEL_VIEW.,$);
#12=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#10,$,.GRAPH_VIEW.,$);
#13=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#10,$,.MODEL_VIEW.,$);
#14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#9,$);
#15=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Plan',*,*,*,*,#14,$,.GRAPH_VIEW.,$);
#16=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#14,$,.PLAN_VIEW.,$);
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#14,$,.SECTION_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#14,$,.ELEVATION_VIEW.,$);
#19=IFCACTORROLE(.ENGINEER.,$,$);
#20=IFCPERSON('krande',$,$,$,$,$,(#19),$);
#21=IFCORGANIZATION('ADA','Assembly Test',$,$,$);
#22=IFCPERSONANDORGANIZATION(#20,#21,$);
#23=IFCAPPLICATION(#21,'XXX','ADA','ADA');
#24=IFCOWNERHISTORY(#22,#23,.READWRITE.,$,$,#22,#23,1695060951);
#25=IFCDIRECTION((0.,0.,1.));
#26=IFCDIRECTION((1.,0.,0.));
#27=IFCCARTESIANPOINT((0.,0.,0.));
#28=IFCAXIS2PLACEMENT3D(#27,#25,#26);
#29=IFCLOCALPLACEMENT($,#28);
#30=IFCSITE('1cQMCZLayHxgf7v5utukF4',#24,'MyFirstIfcFile',$,$,#29,$,$,.ELEMENT.,$,$,$,$,$);
#31=IFCRELAGGREGATES('1cWIcALayHxeWKv5utukF4',#24,'Project Container',$,#1,(#30));
#32=IFCPROPERTYSINGLEVALUE('project',$,IFCTEXT('AdaProject'),$);
#33=IFCPROPERTYSINGLEVALUE('schema',$,IFCTEXT('IFC4X1'),$);
#34=IFCPROPERTYSET('1O_djxZjXASRMw6ucy9_3O',#24,'Properties',$,(#32,#33));
#35=IFCRELDEFINESBYPROPERTIES('1cWIwdLayHxfyzv5utukF4',#24,'Properties',$,(#30),#34);
#36=IFCDIRECTION((0.,0.,1.));
#37=IFCDIRECTION((1.,0.,0.));
#38=IFCCARTESIANPOINT((0.,0.,0.));
#39=IFCAXIS2PLACEMENT3D(#38,#36,#37);
#40=IFCLOCALPLACEMENT(#29,#39);
#41=IFCBUILDINGSTOREY('1cVWQ2LayHxhRev5utukF4',#24,'MyBldg',$,$,#40,$,$,.ELEMENT.,0.);
#42=IFCRELAGGREGATES('1cWIweLayHxeNEv5utukF4',#24,'Site Container',$,#30,(#41));
#43=IFCPROPERTYSINGLEVALUE('ifctype',$,IFCTEXT('building'),$);
#44=IFCPROPERTYSET('2mP4D_J7LCrArYIAqVBbAH',#24,'Properties',$,(#43));
#45=IFCRELDEFINESBYPROPERTIES('1cWIwfLayHxfBov5utukF4',#24,'Properties',$,(#41),#44);
#46=IFCCARTESIANPOINT((-0.1,0.15));
#47=IFCCARTESIANPOINT((0.1,0.15));
#48=IFCCARTESIANPOINT((0.1,-0.15));
#49=IFCCARTESIANPOINT((-0.1,-0.15));
#50=IFCCARTESIANPOINT((-0.1,0.15));
#51=IFCCARTESIANPOINT((-0.09,0.13));
#52=IFCCARTESIANPOINT((0.09,0.13));
#53=IFCCARTESIANPOINT((0.09,-0.13));
#54=IFCCARTESIANPOINT((-0.09,-0.13));
#55=IFCCARTESIANPOINT((-0.09,0.13));
#56=IFCPOLYLINE((#51,#52,#53,#54,#55));
#57=IFCPOLYLINE((#46,#47,#48,#49,#50));
#58=IFCARBITRARYPROFILEDEFWITHVOIDS(.AREA.,'BG300x200x10x20',#57,(#56));
#59=IFCBEAMTYPE('1cPZ5vLayHxg6Fv5utukF4',#24,'BG300x200x10x20','BOX300x200x10x20',$,$,$,$,$,.BEAM.);
#60=IFCISHAPEPROFILEDEF(.AREA.,'IPE220',$,0.11,0.22,0.0059,0.0092,$,$,$);
#61=IFCBEAMTYPE('1cPqF_LayHxgIgv5utukF4',#24,'IPE220','IPE220',$,$,$,$,$,.BEAM.);
#62=IFCMATERIAL('S355',$,'Steel');
#63=IFCPROPERTYSINGLEVALUE('Grade',$,IFCTEXT('S355'),$);
#64=IFCPROPERTYSINGLEVALUE('YieldStress',$,IFCPRESSUREMEASURE(355000000.),$);
#65=IFCPROPERTYSINGLEVALUE('YoungModulus',$,IFCMODULUSOFELASTICITYMEASURE(210000000000.),$);
#66=IFCPROPERTYSINGLEVALUE('PoissonRatio',$,IFCPOSITIVERATIOMEASURE(0.3),$);
#67=IFCPROPERTYSINGLEVALUE('ThermalExpansionCoefficient',$,IFCTHERMALEXPANSIONCOEFFICIENTMEASURE(1.2E-05),$);
#68=IFCPROPERTYSINGLEVALUE('SpecificHeatCapacity',$,IFCSPECIFICHEATCAPACITYMEASURE(1.15),$);
#69=IFCPROPERTYSINGLEVALUE('MassDensity',$,IFCMASSDENSITYMEASURE(7850.),$);
#70=IFCMATERIALPROPERTIES('MaterialMechanical','A Material property description',(#63,#64,#65,#66,#67,#68,#69),#62);
#71=IFCRELASSOCIATESMATERIAL('1cPZ5xLayHxePlv5utukF4',#24,'S355','Objects related to S355',(#129,#164,#196,#227,#258,#289),#62);
#72=IFCMATERIAL('S420',$,'Steel');
#73=IFCPROPERTYSINGLEVALUE('Grade',$,IFCTEXT('S420'),$);
#74=IFCPROPERTYSINGLEVALUE('YieldStress',$,IFCPRESSUREMEASURE(420000000.),$);
#75=IFCPROPERTYSINGLEVALUE('YoungModulus',$,IFCMODULUSOFELASTICITYMEASURE(210000000000.),$);
#76=IFCPROPERTYSINGLEVALUE('PoissonRatio',$,IFCPOSITIVERATIOMEASURE(0.3),$);
#77=IFCPROPERTYSINGLEVALUE('ThermalExpansionCoefficient',$,IFCTHERMALEXPANSIONCOEFFICIENTMEASURE(1.2E-05),$);
#78=IFCPROPERTYSINGLEVALUE('SpecificHeatCapacity',$,IFCSPECIFICHEATCAPACITYMEASURE(1.15),$);
#79=IFCPROPERTYSINGLEVALUE('MassDensity',$,IFCMASSDENSITYMEASURE(7850.),$);
#80=IFCMATERIALPROPERTIES('MaterialMechanical','A Material property description',(#73,#74,#75,#76,#77,#78,#79),#72);
#81=IFCRELASSOCIATESMATERIAL('1cQMCYLayHxexvv5utukF4',#24,'S420','Objects related to S420',(#98),#72);
#82=IFCCARTESIANPOINT((0.,0.,0.));
#83=IFCDIRECTION((0.,0.,1.));
#84=IFCDIRECTION((1.,0.,0.));
#85=IFCAXIS2PLACEMENT3D(#82,#83,#84);
#86=IFCLOCALPLACEMENT($,#85);
#87=IFCCARTESIANPOINT((0.,0.,0.));
#88=IFCDIRECTION((1.,0.,0.));
#89=IFCDIRECTION((0.,0.,1.));
#90=IFCAXIS2PLACEMENT3D(#87,#88,#89);
#91=IFCCARTESIANPOINTLIST2D(((0.,-2.),(0.,0.),(2.,-2.),(2.,0.)),$);
#92=IFCINDEXEDPOLYCURVE(#91,(IFCLINEINDEX((1,2)),IFCLINEINDEX((2,4)),IFCLINEINDEX((4,3)),IFCLINEINDEX((3,1))),.F.);
#93=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#92);
#94=IFCDIRECTION((0.,0.,1.));
#95=IFCEXTRUDEDAREASOLID(#93,#90,#94,0.01);
#96=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#95));
#97=IFCPRODUCTDEFINITIONSHAPE($,$,(#96));
#98=IFCPLATE('1cQMCXLayHxgKdv5utukF4',#24,'pl1','pl1',$,#86,#97,$,$);
#99=IFCCOLOURRGB('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',0.501960784313725,0.501960784313725,0.501960784313725);
#100=IFCSURFACESTYLESHADING(#99,$);
#101=IFCSURFACESTYLE('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',.BOTH.,(#100));
#102=IFCSTYLEDITEM(#95,(#101),'Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)');
#103=IFCDIRECTION((0.,0.,1.));
#104=IFCDIRECTION((1.,0.,0.));
#105=IFCCARTESIANPOINT((0.,0.,0.));
#106=IFCAXIS2PLACEMENT3D(#105,#103,#104);
#107=IFCLOCALPLACEMENT(#40,#106);
#108=IFCCARTESIANPOINT((0.,0.,0.));
#109=IFCDIRECTION((1.,0.,0.));
#110=IFCDIRECTION((0.,1.,0.));
#111=IFCAXIS2PLACEMENT3D(#108,#109,#110);
#112=IFCDIRECTION((0.,0.,1.));
#113=IFCEXTRUDEDAREASOLID(#58,#111,#112,2.);
#114=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#113));
#115=IFCDIRECTION((0.,0.,1.));
#116=IFCDIRECTION((1.,0.,0.));
#117=IFCCARTESIANPOINT((0.,0.,0.));
#118=IFCAXIS2PLACEMENT3D(#117,#115,#116);
#119=IFCLOCALPLACEMENT(#107,#118);
#120=IFCCARTESIANPOINT((0.,0.,0.));
#121=IFCCARTESIANPOINT((2.,0.,0.));
#122=IFCPOLYLINE((#120,#121));
#123=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#122));
#124=IFCCOLOURRGB('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',0.501960784313725,0.501960784313725,0.501960784313725);
#125=IFCSURFACESTYLESHADING(#124,$);
#126=IFCSURFACESTYLE('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',.BOTH.,(#125));
#127=IFCSTYLEDITEM(#113,(#126),'Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)');
#128=IFCPRODUCTDEFINITIONSHAPE($,$,(#114,#123));
#129=IFCBEAM('1cPZ5wLayHxeFFv5utukF4',#24,'MyBeam','BOX300x200x10x20','Beam',#119,#128,$,$);
#130=IFCRELDEFINESBYTYPE('1cWfvcLayHxggTv5utukF4',#24,'BOX',$,(#129),#59);
#131=IFCMATERIALPROFILE('BG300x200x10x20','A material profile',#62,#58,$,'LoadBearing');
#132=IFCMATERIALPROFILESET('BG300x200x10x20',$,(#131),$);
#133=IFCMATERIALPROFILESETUSAGE(#132,5,$);
#134=IFCRELASSOCIATESMATERIAL('1cWio8LayHxeSwv5utukF4',#24,$,$,(#129),#133);
#135=IFCPROPERTYSINGLEVALUE('hidden',$,IFCBOOLEAN(.T.),$);
#136=IFCPROPERTYSET('3S89ko$y96yBG4TM6e0J0K',#24,'Properties',$,(#135));
#137=IFCRELDEFINESBYPROPERTIES('1cXGCQLayHxgByv5utukF4',#24,'Properties',$,(#129),#136);
#138=IFCDIRECTION((0.,0.,1.));
#139=IFCDIRECTION((1.,0.,0.));
#140=IFCCARTESIANPOINT((0.,0.,0.));
#141=IFCAXIS2PLACEMENT3D(#140,#138,#139);
#142=IFCLOCALPLACEMENT(#40,#141);
#143=IFCCARTESIANPOINT((0.,0.,0.));
#144=IFCDIRECTION((1.,0.,0.));
#145=IFCDIRECTION((0.,1.,0.));
#146=IFCAXIS2PLACEMENT3D(#143,#144,#145);
#147=IFCDIRECTION((0.,0.,1.));
#148=IFCEXTRUDEDAREASOLID(#60,#146,#147,2.);
#149=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#148));
#150=IFCDIRECTION((0.,0.,1.));
#151=IFCDIRECTION((1.,0.,0.));
#152=IFCCARTESIANPOINT((0.,0.,0.));
#153=IFCAXIS2PLACEMENT3D(#152,#150,#151);
#154=IFCLOCALPLACEMENT(#142,#153);
#155=IFCCARTESIANPOINT((0.,0.,0.));
#156=IFCCARTESIANPOINT((2.,0.,0.));
#157=IFCPOLYLINE((#155,#156));
#158=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#157));
#159=IFCCOLOURRGB('Color(red=1.0, green=0.0, blue=0.0, opacity=1.0)',1.,0.,0.);
#160=IFCSURFACESTYLESHADING(#159,$);
#161=IFCSURFACESTYLE('Color(red=1.0, green=0.0, blue=0.0, opacity=1.0)',.BOTH.,(#160));
#162=IFCSTYLEDITEM(#148,(#161),'Color(red=1.0, green=0.0, blue=0.0, opacity=1.0)');
#163=IFCPRODUCTDEFINITIONSHAPE($,$,(#149,#158));
#164=IFCBEAM('1cPbYcLayHxe$Cv5utukF4',#24,'bm1','IPE220','Beam',#154,#163,$,$);
#165=IFCRELDEFINESBYTYPE('1cXLnKLayHxhQQv5utukF4',#24,'I',$,(#164,#196,#227,#258,#289),#61);
#166=IFCMATERIALPROFILE('IPE220','A material profile',#62,#60,$,'LoadBearing');
#167=IFCMATERIALPROFILESET('IPE220',$,(#166),$);
#168=IFCMATERIALPROFILESETUSAGE(#167,5,$);
#169=IFCRELASSOCIATESMATERIAL('1cXLnLLayHxetXv5utukF4',#24,$,$,(#164),#168);
#170=IFCDIRECTION((0.,0.,1.));
#171=IFCDIRECTION((1.,0.,0.));
#172=IFCCARTESIANPOINT((0.,0.,0.));
#173=IFCAXIS2PLACEMENT3D(#172,#170,#171);
#174=IFCLOCALPLACEMENT(#40,#173);
#175=IFCCARTESIANPOINT((0.,0.,0.));
#176=IFCDIRECTION((0.,1.,0.));
#177=IFCDIRECTION((-1.,0.,0.));
#178=IFCAXIS2PLACEMENT3D(#175,#176,#177);
#179=IFCDIRECTION((0.,0.,1.));
#180=IFCEXTRUDEDAREASOLID(#60,#178,#179,2.);
#181=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#180));
#182=IFCDIRECTION((0.,0.,1.));
#183=IFCDIRECTION((1.,0.,0.));
#184=IFCCARTESIANPOINT((0.,0.,0.));
#185=IFCAXIS2PLACEMENT3D(#184,#182,#183);
#186=IFCLOCALPLACEMENT(#174,#185);
#187=IFCCARTESIANPOINT((0.,0.,0.));
#188=IFCCARTESIANPOINT((0.,2.,0.));
#189=IFCPOLYLINE((#187,#188));
#190=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#189));
#191=IFCCOLOURRGB('Color(red=0.0, green=0.0, blue=1.0, opacity=1.0)',0.,0.,1.);
#192=IFCSURFACESTYLESHADING(#191,$);
#193=IFCSURFACESTYLE('Color(red=0.0, green=0.0, blue=1.0, opacity=1.0)',.BOTH.,(#192));
#194=IFCSTYLEDITEM(#180,(#193),'Color(red=0.0, green=0.0, blue=1.0, opacity=1.0)');
#195=IFCPRODUCTDEFINITIONSHAPE($,$,(#181,#190));
#196=IFCBEAM('1cPsijLayHxgUev5utukF4',#24,'bm2','IPE220','Beam',#186,#195,$,$);
#197=IFCMATERIALPROFILE('IPE220','A material profile',#62,#60,$,'LoadBearing');
#198=IFCMATERIALPROFILESET('IPE220',$,(#197),$);
#199=IFCMATERIALPROFILESETUSAGE(#198,5,$);
#200=IFCRELASSOCIATESMATERIAL('1cXTDfLayHxe6qv5utukF4',#24,$,$,(#196),#199);
#201=IFCDIRECTION((0.,0.,1.));
#202=IFCDIRECTION((1.,0.,0.));
#203=IFCCARTESIANPOINT((0.,0.,0.));
#204=IFCAXIS2PLACEMENT3D(#203,#201,#202);
#205=IFCLOCALPLACEMENT(#40,#204);
#206=IFCCARTESIANPOINT((0.,0.,0.));
#207=IFCDIRECTION((0.,0.,1.));
#208=IFCDIRECTION((1.,0.,0.));
#209=IFCAXIS2PLACEMENT3D(#206,#207,#208);
#210=IFCDIRECTION((0.,0.,1.));
#211=IFCEXTRUDEDAREASOLID(#60,#209,#210,2.);
#212=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#211));
#213=IFCDIRECTION((0.,0.,1.));
#214=IFCDIRECTION((1.,0.,0.));
#215=IFCCARTESIANPOINT((0.,0.,0.));
#216=IFCAXIS2PLACEMENT3D(#215,#213,#214);
#217=IFCLOCALPLACEMENT(#205,#216);
#218=IFCCARTESIANPOINT((0.,0.,0.));
#219=IFCCARTESIANPOINT((0.,0.,2.));
#220=IFCPOLYLINE((#218,#219));
#221=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#220));
#222=IFCCOLOURRGB('Color(red=0.0, green=1.0, blue=0.0, opacity=1.0)',0.,1.,0.);
#223=IFCSURFACESTYLESHADING(#222,$);
#224=IFCSURFACESTYLE('Color(red=0.0, green=1.0, blue=0.0, opacity=1.0)',.BOTH.,(#223));
#225=IFCSTYLEDITEM(#211,(#224),'Color(red=0.0, green=1.0, blue=0.0, opacity=1.0)');
#226=IFCPRODUCTDEFINITIONSHAPE($,$,(#212,#221));
#227=IFCBEAM('1cPv7pLayHxerkv5utukF4',#24,'bm3','IPE220','Beam',#217,#226,$,$);
#228=IFCMATERIALPROFILE('IPE220','A material profile',#62,#60,$,'LoadBearing');
#229=IFCMATERIALPROFILESET('IPE220',$,(#228),$);
#230=IFCMATERIALPROFILESETUSAGE(#229,5,$);
#231=IFCRELASSOCIATESMATERIAL('1cXYirLayHxe0mv5utukF4',#24,$,$,(#227),#230);
#232=IFCDIRECTION((0.,0.,1.));
#233=IFCDIRECTION((1.,0.,0.));
#234=IFCCARTESIANPOINT((0.,0.,0.));
#235=IFCAXIS2PLACEMENT3D(#234,#232,#233);
#236=IFCLOCALPLACEMENT(#40,#235);
#237=IFCCARTESIANPOINT((0.,0.,0.));
#238=IFCDIRECTION((0.707106781186547,0.,0.707106781186547));
#239=IFCDIRECTION((0.,1.,0.));
#240=IFCAXIS2PLACEMENT3D(#237,#238,#239);
#241=IFCDIRECTION((0.,0.,1.));
#242=IFCEXTRUDEDAREASOLID(#60,#240,#241,2.82842712474619);
#243=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#242));
#244=IFCDIRECTION((0.,0.,1.));
#245=IFCDIRECTION((1.,0.,0.));
#246=IFCCARTESIANPOINT((0.,0.,0.));
#247=IFCAXIS2PLACEMENT3D(#246,#244,#245);
#248=IFCLOCALPLACEMENT(#236,#247);
#249=IFCCARTESIANPOINT((0.,0.,0.));
#250=IFCCARTESIANPOINT((2.,0.,2.));
#251=IFCPOLYLINE((#249,#250));
#252=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#251));
#253=IFCCOLOURRGB('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',0.501960784313725,0.501960784313725,0.501960784313725);
#254=IFCSURFACESTYLESHADING(#253,$);
#255=IFCSURFACESTYLE('Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)',.BOTH.,(#254));
#256=IFCSTYLEDITEM(#242,(#255),'Color(red=0.5019607843137255, green=0.5019607843137255, blue=0.5019607843137255, opacity=1.0)');
#257=IFCPRODUCTDEFINITIONSHAPE($,$,(#243,#252));
#258=IFCBEAM('1cPxbBLayHxfhtv5utukF4',#24,'bm4','IPE220','Beam',#248,#257,$,$);
#259=IFCMATERIALPROFILE('IPE220','A material profile',#62,#60,$,'LoadBearing');
#260=IFCMATERIALPROFILESET('IPE220',$,(#259),$);
#261=IFCMATERIALPROFILESETUSAGE(#260,5,$);
#262=IFCRELASSOCIATESMATERIAL('1cXgAfLayHxf$bv5utukF4',#24,$,$,(#258),#261);
#263=IFCDIRECTION((0.,0.,1.));
#264=IFCDIRECTION((1.,0.,0.));
#265=IFCCARTESIANPOINT((0.,0.,0.));
#266=IFCAXIS2PLACEMENT3D(#265,#263,#264);
#267=IFCLOCALPLACEMENT(#40,#266);
#268=IFCCARTESIANPOINT((0.,0.,2.));
#269=IFCDIRECTION((1.,0.,0.));
#270=IFCDIRECTION((0.,1.,0.));
#271=IFCAXIS2PLACEMENT3D(#268,#269,#270);
#272=IFCDIRECTION((0.,0.,1.));
#273=IFCEXTRUDEDAREASOLID(#60,#271,#272,2.);
#274=IFCSHAPEREPRESENTATION(#11,'Body','SolidModel',(#273));
#275=IFCDIRECTION((0.,0.,1.));
#276=IFCDIRECTION((1.,0.,0.));
#277=IFCCARTESIANPOINT((0.,0.,0.));
#278=IFCAXIS2PLACEMENT3D(#277,#275,#276);
#279=IFCLOCALPLACEMENT(#267,#278);
#280=IFCCARTESIANPOINT((0.,0.,2.));
#281=IFCCARTESIANPOINT((2.,0.,2.));
#282=IFCPOLYLINE((#280,#281));
#283=IFCSHAPEREPRESENTATION(#12,'Axis','Curve3D',(#282));
#284=IFCCOLOURRGB('Color(red=1.0, green=1.0, blue=1.0, opacity=1.0)',1.,1.,1.);
#285=IFCSURFACESTYLESHADING(#284,$);
#286=IFCSURFACESTYLE('Color(red=1.0, green=1.0, blue=1.0, opacity=1.0)',.BOTH.,(#285));
#287=IFCSTYLEDITEM(#273,(#286),'Color(red=1.0, green=1.0, blue=1.0, opacity=1.0)');
#288=IFCPRODUCTDEFINITIONSHAPE($,$,(#274,#283));
#289=IFCBEAM('1cQ0RLLayHxflGv5utukF4',#24,'bm5','IPE220','Beam',#279,#288,$,$);
#290=IFCMATERIALPROFILE('IPE220','A material profile',#62,#60,$,'LoadBearing');
#291=IFCMATERIALPROFILESET('IPE220',$,(#290),$);
#292=IFCMATERIALPROFILESETUSAGE(#291,5,$);
#293=IFCRELASSOCIATESMATERIAL('1cXlcyLayHxhTpv5utukF4',#24,$,$,(#289),#292);
#294=IFCRELCONTAINEDINSPATIALSTRUCTURE('1cXlczLayHxfxNv5utukF4',#24,'Physical model',$,(#98,#129,#164,#196,#227,#258,#289),#41);
ENDSEC;
END-ISO-10303-21;

267
main.py Normal file
View File

@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""
STP到GLB转换工具 - Python包装器
调用STP2GLB.exe的友好命令行接口
"""
import argparse
import os
import sys
import subprocess
import pathlib
from typing import List, Optional, Dict, Any
# 质量预设配置
QUALITY_PRESETS = {
'low': {'linear': 0.5, 'angular': 0.8},
'medium': {'linear': 0.1, 'angular': 0.5}, # 默认值
'high': {'linear': 0.01, 'angular': 0.1},
'ultra': {'linear': 0.001, 'angular': 0.05},
'custom': None # 使用用户指定值
}
def find_stp2glb_executable() -> Optional[str]:
"""查找STP2GLB.exe可执行文件"""
# 1. 检查环境变量
if 'STP2GLB_PATH' in os.environ:
exe_path = os.environ['STP2GLB_PATH']
if os.path.isfile(exe_path):
return exe_path
# 2. 当前目录
current_dir = pathlib.Path.cwd()
exe_candidates = ['STP2GLB.exe', 'STP2GLB']
for exe_name in exe_candidates:
exe_path = current_dir / exe_name
if exe_path.is_file():
return str(exe_path)
# 3. Pixi环境目录
pixi_dirs = [
current_dir / '.pixi' / 'envs' / 'dynamic' / 'Library' / 'bin',
current_dir / '.pixi' / 'envs' / 'dynamic-debug' / 'Library' / 'bin',
current_dir / '.pixi' / 'envs' / 'static' / 'Library' / 'bin',
]
for pixi_dir in pixi_dirs:
for exe_name in exe_candidates:
exe_path = pixi_dir / exe_name
if exe_path.is_file():
return str(exe_path)
# 4. 构建目录
build_dirs = list(current_dir.glob('build/*/'))
for build_dir in build_dirs:
for exe_name in exe_candidates:
exe_path = build_dir / exe_name
if exe_path.is_file():
return str(exe_path)
return None
def validate_input_file(input_path: str) -> bool:
"""验证输入文件"""
if not os.path.isfile(input_path):
print(f"错误: 输入文件不存在: {input_path}")
return False
# 检查文件扩展名
ext = pathlib.Path(input_path).suffix.lower()
if ext not in ['.stp', '.step']:
print(f"错误: 输入文件必须是.stp或.step格式当前: {ext}")
return False
return True
def validate_output_file(output_path: str) -> bool:
"""验证输出文件"""
# 检查文件扩展名
ext = pathlib.Path(output_path).suffix.lower()
if ext != '.glb':
print(f"错误: 输出文件必须是.glb格式当前: {ext}")
return False
# 检查目录是否存在
output_dir = pathlib.Path(output_path).parent
if not output_dir.exists():
print(f"错误: 输出目录不存在: {output_dir}")
return False
# 警告如果文件已存在
if os.path.exists(output_path):
print(f"警告: 输出文件已存在,将被覆盖: {output_path}")
return True
def build_command_args(args: argparse.Namespace, exe_path: str) -> List[str]:
"""构建命令行参数"""
cmd_args = [exe_path]
# 必需参数
cmd_args.extend(['--stp', args.input])
cmd_args.extend(['--glb', args.output])
# 质量预设或自定义偏差值
if args.quality == 'custom':
if args.linear_deflection is not None:
cmd_args.extend(['--lin-defl', str(args.linear_deflection)])
if args.angular_deflection is not None:
cmd_args.extend(['--ang-defl', str(args.angular_deflection)])
else:
preset = QUALITY_PRESETS[args.quality]
cmd_args.extend(['--lin-defl', str(preset['linear'])])
cmd_args.extend(['--ang-defl', str(preset['angular'])])
# 可选标志
if args.debug:
cmd_args.append('--debug')
if args.solid_only:
cmd_args.append('--solid-only')
return cmd_args
def run_conversion(cmd_args: List[str]) -> int:
"""运行转换程序"""
try:
print("开始转换...")
print(f"执行命令: {' '.join(cmd_args)}")
print("-" * 50)
# 运行命令并实时显示输出
process = subprocess.run(
cmd_args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8'
)
# 显示输出
if process.stdout:
print(process.stdout)
if process.returncode == 0:
print("-" * 50)
print("转换完成!")
else:
print("-" * 50)
print(f"转换失败,退出码: {process.returncode}")
return process.returncode
except Exception as e:
print(f"执行错误: {e}")
return 1
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description='STP到GLB转换工具 - 默认使用层级转换并保留原始组件名称',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# 位置参数
parser.add_argument('input', help='输入的STP文件路径')
parser.add_argument('output', help='输出的GLB文件路径')
# 可选参数
parser.add_argument(
'--linear-deflection',
type=float,
help='线性偏差 (仅在quality=custom时生效)'
)
parser.add_argument(
'--angular-deflection',
type=float,
help='角度偏差 (仅在quality=custom时生效)'
)
parser.add_argument(
'--quality',
choices=['low', 'medium', 'high', 'ultra', 'custom'],
default='medium',
help='网格质量等级 (默认: medium)'
)
parser.add_argument(
'--debug',
action='store_true',
help='调试模式,提供详细的转换信息'
)
parser.add_argument(
'--solid-only',
action='store_true',
help='仅处理实体几何'
)
parser.add_argument(
'--no-scale',
action='store_true',
help='禁用自动缩放 (预留功能)'
)
parser.add_argument(
'--no-center',
action='store_true',
help='禁用自动居中 (预留功能)'
)
args = parser.parse_args()
# 验证自定义质量参数
if args.quality == 'custom':
if args.linear_deflection is None and args.angular_deflection is None:
print("错误: 使用custom质量等级时必须指定--linear-deflection或--angular-deflection")
return 1
# 验证输入输出文件
if not validate_input_file(args.input):
return 1
if not validate_output_file(args.output):
return 1
# 查找可执行文件
exe_path = find_stp2glb_executable()
if not exe_path:
print("错误: 未找到STP2GLB.exe可执行文件")
print("请确保:")
print("1. 已编译项目 (pixi run build && pixi run install)")
print("2. 或设置环境变量STP2GLB_PATH指向可执行文件")
return 1
print(f"使用可执行文件: {exe_path}")
# 显示质量预设信息
if args.quality != 'custom':
preset = QUALITY_PRESETS[args.quality]
print(f"质量等级: {args.quality}")
print(f"线性偏差: {preset['linear']}, 角度偏差: {preset['angular']}")
else:
print("质量等级: custom")
if args.linear_deflection is not None:
print(f"线性偏差: {args.linear_deflection}")
if args.angular_deflection is not None:
print(f"角度偏差: {args.angular_deflection}")
# 预留功能提示
if args.no_scale:
print("注意: --no-scale 功能暂未实现")
if args.no_center:
print("注意: --no-center 功能暂未实现")
# 构建命令参数
cmd_args = build_command_args(args, exe_path)
# 运行转换
return run_conversion(cmd_args)
if __name__ == '__main__':
sys.exit(main())

BIN
out.glb Normal file

Binary file not shown.

BIN
out1.glb Normal file

Binary file not shown.

3644
pixi.lock generated Normal file

File diff suppressed because it is too large Load Diff

118
pixi.toml Normal file
View File

@ -0,0 +1,118 @@
[project]
name = "stp2glb"
version = "1.1.0"
description = "A STEP to GLB converter"
channels = ["https://repo.prefix.dev/adapy-tools", "conda-forge"]
platforms = ["win-64", "linux-64"]
[environments]
dynamic-shared = { features=["dynamic-shared"], no-default-feature = true }
dynamic-debug = { features=["dynamic-debug", "dynamic-shared"], no-default-feature = true }
dynamic = { features=["dynamic", "dynamic-shared"], no-default-feature = true }
static = { features=["static"], no-default-feature = true }
static-mingw = { features=["static-mingw"], no-default-feature = true }
conda = { features=["conda"], no-default-feature = true }
# Dependencies
[feature.conda.dependencies]
rattler-build="<0.35"
[feature.dynamic-shared.dependencies]
c-compiler = "*"
cxx-compiler = "*"
cmake = "==3.30.5"
cli11 = "*"
ninja = "*"
nlohmann_json = "*"
tbb-devel = "*"
# Optional dependencies
tinygltf = "==2.8.19"
cgal-cpp = "==5.6.1"
pytest = "==7.3.1"
python = "3.12.*"
[feature.dynamic.target.linux-64.dependencies]
occt = { version = "==7.8.1", build="*novtk*" }
ifcopenshell = "*"
[feature.dynamic.target.win-64.dependencies]
vs2022_win-64 = "*"
vswhere = "*"
occt = { version = "==7.8.1", build = "*novtk*", channel = "conda-forge" }
stepcode = { version = "==0.8.2" }
ifcopenshell = { version = "*" }
[feature.dynamic-debug.target.win-64.dependencies]
vs2022_win-64 = "*"
vswhere = "*"
occt = { version = "==7.8.1", build = "*novtk*debug*", channel = "https://repo.prefix.dev/adapy-tools" }
stepcode = { version = "==0.8.2", build = "*debug*" }
ifcopenshell = { version = "*", build = "*debug*" }
[feature.static.dependencies]
cmake = "==3.30.5"
cli11 = "*"
ninja = "*"
nlohmann_json = "*"
[feature.static.target.linux-64.dependencies]
c-compiler = "*"
cxx-compiler = "*"
lld="*"
occt = { version = "==7.8.1", build = "*static_novtk*", channel = "https://repo.prefix.dev/adapy-tools" }
[feature.static.target.win-64.dependencies]
vs2022_win-64 = "*"
vswhere = "*"
freeimage = "*"
occt = { version = "==7.8.1", build = "*static_novtk*", channel = "https://repo.prefix.dev/adapy-tools" }
[feature.static-mingw.target.win-64.dependencies]
cmake = "==3.30.5"
cli11 = "*"
ninja = "*"
nlohmann_json = "*"
m2w64-sysroot_win-64 = "*"
gcc_win-64 = "13*"
gxx_win-64 = "13*"
ld_impl_win-64 = "*"
#occt = { version = "==7.8.1", build = "*static*", channel = "https://repo.prefix.dev/adapy-tools" }
# TASKS
[feature.dynamic.target.win-64.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "win-dynamic-release", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/win-dynamic-release", "install"], description = "Install the project" }
test = { cmd = ["ctest"], cwd="build/win-dynamic-release", description = "Run the tests" }
[feature.dynamic-debug.target.win-64.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "win-dynamic-debug", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/win-dynamic-debug", "install"], description = "Install the project" }
test = { cmd = ["ctest"], cwd="build/win-dynamic-debug", description = "Run the tests" }
[feature.dynamic.target.linux-64.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "linux-dynamic", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/linux-dynamic", "install"], description = "Install the project" }
test = { cmd = ["ctest"], cwd="build/linux-local", description = "Run the tests" }
[feature.static.target.win-64.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "win-static", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/win-static", "install"], description = "Install the project" }
[feature.static.target.linux-64.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "linux-static", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/linux-static", "install"], description = "Install the project" }
[feature.static-mingw.tasks]
build = { cmd=["cmake","-G","Ninja", "--preset", "win-static-mingw", "--fresh"] }
install = { cmd = ["ninja", "-C", "build/win-static-mingw", "install"], description = "Install the project" }
[feature.conda.target.win-64.tasks]
build-occt = { cmd=["rattler-build", "build", "-r", "recipes/occt-static/recipe.yaml", "-m","recipes/occt-static/conda_build_config-win.yaml"] }
build-stepcode = { cmd=["rattler-build", "build", "-r", "recipes/stepcode-static/recipe.yaml", "-m","recipes/stepcode-static/conda_build_config-win.yaml"] }
[feature.conda.target.linux-64.tasks]
build-occt = { cmd=["rattler-build", "build", "-r", "recipes/occt-static/recipe.yaml", "-m","recipes/occt-static/conda_build_config-linux.yaml"] }
build-stepcode = { cmd=["rattler-build", "build", "-r", "recipes/stepcode-static/recipe.yaml", "-m","recipes/stepcode-static/conda_build_config-linux.yaml"] }

View File

@ -0,0 +1,10 @@
# a little test to see if occt is installed
project(occt_test_cmake NONE)
cmake_minimum_required(VERSION 3.5)
find_package(OpenCASCADE)
if (OpenCASCADE_MAJOR_VERSION)
message(STATUS " Found OpenCASCADE version: ${OpenCASCADE_MAJOR_VERSION}.${OpenCASCADE_MINOR_VERSION}.${OpenCASCADE_MAINTENANCE_VERSION}")
else(OpenCASCADE_MAJOR_VERSION)
message(FATAL_ERROR "could not find OpenCASCADE")
endif(OpenCASCADE_MAJOR_VERSION)

View File

@ -0,0 +1,36 @@
@echo OFF
echo "Building OpenCASCADE Technology"
set FNAME=occt-%PKG_VERSION%.tar.gz
REM Extract the .tar.gz file to the current directory
7z x %FNAME% -aoa -o.
REM Extract the .tar file to the current directory
7z x occt-%PKG_VERSION%.tar -aoa -o. occt*
REM Copy the contents from occt*/ to the current directory
for /D %%d in (occt*) do xcopy "%%d\*" . /E /H /K /Y /Q
cmake -S . -B build -G Ninja ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_LIBRARY_PATH:FILEPATH="%LIBRARY_PREFIX%/lib" ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D INSTALL_DIR_LAYOUT="Unix" ^
-D BUILD_MODULE_Draw:BOOL=OFF ^
-D 3RDPARTY_DIR:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_BUILD_TYPE="Release" ^
-D USE_TBB:BOOL=OFF ^
-D USE_VTK:BOOL=OFF ^
-D USE_FREEIMAGE:BOOL=ON ^
-D USE_RAPIDJSON:BOOL=ON ^
-D BUILD_LIBRARY_TYPE="Static" ^
-D BUILD_RELEASE_DISABLE_EXCEPTIONS:BOOL=OFF ^
-D CMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^
-D CMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
if errorlevel 1 exit 1
cmake --build build -- -v install
if errorlevel 1 exit 1

View File

@ -0,0 +1,26 @@
# extract tar.gz to this dir
tar --strip-components=1 -xvzf occt-${PKG_VERSION}.tar.gz
# Failing on exptocasexe-undefined-reference
# https://dev.opencascade.org/content/installing-occt-libraries-static-libraries-fails-ubuntu-2204-exptocasexe-undefined-reference
cmake -S . -B build -G Ninja \
-D CMAKE_FIND_ROOT_PATH="$PREFIX;$BUILD_PREFIX/$HOST/sysroot" \
-D CMAKE_INSTALL_PREFIX:FILEPATH=$PREFIX \
-D CMAKE_PREFIX_PATH:FILEPATH=$PREFIX \
-D 3RDPARTY_DIR:FILEPATH=$PREFIX \
-D BUILD_MODULE_Draw:BOOL=OFF \
-D USE_TBB:BOOL=OFF \
-D CMAKE_BUILD_TYPE:STRING="Release" \
-D BUILD_RELEASE_DISABLE_EXCEPTIONS=OFF \
-D USE_VTK:BOOL=OFF \
-D USE_FREEIMAGE:BOOL=ON \
-D USE_RAPIDJSON:BOOL=ON \
-D BUILD_RELEASE_DISABLE_EXCEPTIONS:BOOL=OFF \
-D BUILD_LIBRARY_TYPE="Static" \
-D CMAKE_VERBOSE_MAKEFILE:BOOL=ON \
-D BUILD_MODULE_DETools:BOOL=OFF \
-D CMAKE_EXE_LINKER_FLAGS="-lpthread -ldl -lm" \
-D BUILD_SHARED_LIBS:BOOL=OFF
cmake --build build -- install

View File

@ -0,0 +1,25 @@
c_stdlib:
- sysroot
c_stdlib_version:
- '2.17'
cdt_name:
- cos7
channel_sources:
- conda-forge
channel_targets:
- conda-forge main
cxx_compiler:
- gxx
cxx_compiler_version:
- '13'
docker_image:
- quay.io/condaforge/linux-anvil-cos7-x86_64
fontconfig:
- '2'
freetype:
- '2'
target_platform:
- linux-64
zip_keys:
- - c_stdlib_version
- cdt_name

View File

@ -0,0 +1,28 @@
c_compiler:
- vs2022
c_stdlib:
- vs
channel_sources:
- conda-forge
channel_targets:
- conda-forge main
cxx_compiler:
- vs2022
gmp:
- '6'
libboost_devel:
- '1.86'
libxml2:
- '2'
mpfr:
- '4'
pin_run_as_build:
python:
min_pin: x.x
max_pin: x.x
python:
- 3.12.* *_cpython
target_platform:
- win-64
zlib:
- '1'

View File

@ -0,0 +1,82 @@
schema_version: 1
context:
name: occt
version: 7.8.1
build: 3
package:
name: ${{ name }}
version: ${{ version }}
source:
url: http://git.dev.opencascade.org/gitweb/?p=occt.git;a=snapshot;h=V${{ version | replace(".", "_") }};sf=tgz
sha256: 006319a932cb8d76f32455bf4524eca2100657cb0cc65526754fd8da83b3924f
file_name: ${{ name }}-${{ version }}.tar.gz
build:
number: ${{ build }}
string: static_novtk_h${{ hash }}_${{ build }}
requirements:
build:
- ${{ compiler('cxx') }}
- ${{ stdlib("c") }}
- cmake
- ninja
- if: win
then:
- 7zip
- if: linux
then:
- ${{ cdt('mesa-libgl-devel') }}
- ${{ cdt('libxi-devel') }}
- ${{ cdt('libxext-devel') }}
host:
- freetype
- freeimage
- fontconfig
- if: linux
then:
- xorg-libx11
- xorg-xproto
- pthread-stubs
- rapidjson
run:
- freetype
- freeimage
- fontconfig
- if: linux
then: xorg-libxt
- rapidjson
run_exports:
- ${{ pin_subpackage('occt', upper_bound='x.x.x') }}
tests:
- files:
recipe:
- CMakeLists.txt
requirements:
run:
- ninja
- cmake
script:
- "cmake -G \"Ninja\" ."
about:
license: LGPL-2.1-only
license_file: LICENSE_LGPL_21.txt
summary: this is the occ (opencascade) CAD-Kernel
description: |
Open Cascade Technology (OCCT), formerly called CAS.CADE
is an open source software development platform for 3D CAD,
CAM, CAE, etc. that is developed and supported by Open Cascade SAS.
homepage: https://www.opencascade.com/
repository: http://git.dev.opencascade.org/gitweb/?p=occt.git
documentation: https://www.opencascade.com/content/documentation
extra:
recipe-maintainers:
- adrianinsaval
- looooo

View File

@ -0,0 +1,15 @@
cmake -S . -B build -G Ninja ^
-D CMAKE_BUILD_TYPE="Release" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_LIBRARY_PATH:FILEPATH="%LIBRARY_PREFIX%/lib" ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D BUILD_SHARED_LIBS=OFF ^
-D BUILD_STATIC_LIBS=ON ^
-D CMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^
-D CMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
if errorlevel 1 exit 1
cmake --build build -- -v install
if errorlevel 1 exit 1

View File

@ -0,0 +1,13 @@
cmake -S . -B build -G Ninja \
-D SC_BUILD_TYPE="Release" \
-D CMAKE_BUILD_TYPE="Release" \
-D CMAKE_PREFIX_PATH:FILEPATH="${PREFIX}" \
-D CMAKE_LIBRARY_PATH:FILEPATH="${PREFIX}/lib" \
-D CMAKE_INSTALL_PREFIX:FILEPATH="${PREFIX}" \
-D BUILD_SHARED_LIBS=OFF \
-D ENABLE_ALL=ON \
-D BUILD_STATIC_LIBS=ON \
-D SC_CPP_GENERATOR:BOOL=ON \
-D SC_PYTHON_GENERATOR:BOOL=ON
cmake --build build -- -v install

View File

@ -0,0 +1,25 @@
c_stdlib:
- sysroot
c_stdlib_version:
- '2.17'
cdt_name:
- cos7
channel_sources:
- conda-forge
channel_targets:
- conda-forge main
cxx_compiler:
- gxx
cxx_compiler_version:
- '13'
docker_image:
- quay.io/condaforge/linux-anvil-cos7-x86_64
fontconfig:
- '2'
freetype:
- '2'
target_platform:
- linux-64
zip_keys:
- - c_stdlib_version
- cdt_name

View File

@ -0,0 +1,14 @@
c_compiler:
- vs2022
c_stdlib:
- vs
channel_sources:
- conda-forge
channel_targets:
- conda-forge main
cxx_compiler:
- vs2022
target_platform:
- win-64
zlib:
- '1'

View File

@ -0,0 +1,48 @@
schema_version: 1
context:
name: stepcode
version: 0.8.2
build: 0
package:
name: ${{ name }}
version: ${{ version }}
source:
url: https://github.com/stepcode/stepcode/archive/refs/tags/v${{ version }}.tar.gz
sha256: a0307185313a55e127db4476bf234b9ea90edfeee6ac7a57923f7608a48d91e5
build:
number: ${{ build }}
string: static_h${{ PKG_HASH }}_${{ build }}
requirements:
build:
- ${{ compiler('cxx') }}
- ${{ stdlib("c") }}
- if: unix
then: lld
- cmake
- ninja
run_exports:
- ${{ pin_subpackage(name, upper_bound='x.x.x') }}
about:
license: 0BSD
license_file: COPYING
summary: Data Exchange with ISO 10303 (STEP) standards
description: |
STEPcode (formerly NIST's STEP Class Library) is used with IFC, STEP,
and other standards that utilize the technologies of ISO10303 (STEP).
It generates C++ and Python from EXPRESS (10303-11) schemas. The code is
capable of reading and writing STEP Part 21 exchange files.
It also utilizes Parts 22 and 23 (SDAI and its C++ binding).
homepage: https://stepcode.github.io/
repository: https://github.com/stepcode/stepcode
documentation: https://stepcode.github.io/docs/home/
extra:
recipe-maintainers:
- Krande

37
src/cadit/ifc/ifcop.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "ifcop.h"
#include "ifcparse/Ifc4x1.h"
#include "ifcparse/IfcFile.h"
//#include "ifcparse/IfcHierarchyHelper.h"
//#include "ifcparse/IfcBaseClass.h"
//#include "ifcgeom_schema_agnostic/Serialization.h"
#define IfcSchema Ifc4x1
using namespace std::string_literals;
int read_ifc_file(const std::string &file_name) {
IfcParse::IfcFile file(file_name);
if (!file.good()) {
std::cout << "Unable to parse .ifc file" << std::endl;
return 1;
}
// std::cout << "file name: " << file.header().file_name().name() << std::endl;
IfcSchema::IfcBeam::list::ptr elements = file.instances_by_type<IfcSchema::IfcBeam>();
std::cout << "Found " << elements->size() << " elements in " << file_name << ":" << std::endl;
for (auto element : *elements) {
std::cout << element->data().toString() << std::endl;
}
return 0;
}
void ifc_module(nb::module_ &m) {
m.def("read_ifc_file", &read_ifc_file, "file_name"_a, "Read an ifc file");
}

4
src/cadit/ifc/ifcop.h Normal file
View File

@ -0,0 +1,4 @@
#include "../../binding_core.h"
void ifc_module(nb::module_ &m);

View File

@ -0,0 +1,115 @@
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom2d_Line.hxx>
#include <TColgp_Array2OfPnt.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Face.hxx>
#include <STEPControl_Writer.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Edge.hxx>
#include <BRep_Builder.hxx>
#include <gp_Lin2d.hxx>
#include <gp_Pnt2d.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <GeomAPI_ProjectPointOnSurf.hxx>
#include <Precision.hxx>
#include <TopLoc_Location.hxx> // For location
#include <filesystem>
#include "../../config_structs.h"
void make_a_bspline_surf(const GlobalConfig& config) {
// Define the control points for the B-Spline surface from STEP file
TColgp_Array2OfPnt controlPoints(1, 4, 1, 2); // Adjust range as needed
controlPoints.SetValue(1, 1, gp_Pnt(-0.5, 0.5, 0.0));
controlPoints.SetValue(2, 1, gp_Pnt(-0.561, 0.272, 0.333));
controlPoints.SetValue(3, 1, gp_Pnt(-0.622, 0.045, 0.667));
controlPoints.SetValue(4, 1, gp_Pnt(-0.683, -0.183, 1.0));
controlPoints.SetValue(1, 2, gp_Pnt(-0.5, -0.5, 0.0));
controlPoints.SetValue(2, 2, gp_Pnt(-0.272, -0.561, 0.333));
controlPoints.SetValue(3, 2, gp_Pnt(-0.045, -0.622, 0.667));
controlPoints.SetValue(4, 2, gp_Pnt(0.183, -0.683, 1.0));
// Define knots and multiplicities
TColStd_Array1OfReal uKnots(1, 2);
uKnots.SetValue(1, 0.0);
uKnots.SetValue(2, 1224.744871392); // From STEP data
TColStd_Array1OfReal vKnots(1, 2);
vKnots.SetValue(1, 3.0);
vKnots.SetValue(2, 4.0); // From STEP data
TColStd_Array1OfInteger uMults(1, 2);
uMults.SetValue(1, 4);
uMults.SetValue(2, 4);
TColStd_Array1OfInteger vMults(1, 2);
vMults.SetValue(1, 2);
vMults.SetValue(2, 2);
// Create the B-Spline surface
Handle(Geom_BSplineSurface) bsplineSurface = new Geom_BSplineSurface(
controlPoints, uKnots, vKnots, uMults, vMults, 3, 1, Standard_False, Standard_False
);
// Create boundary points and 3D edges
gp_Pnt p1(-0.5, 0.5, 0.0); // Point from #55
gp_Pnt p2(-0.5, -0.5, 0.0); // Point from #56
gp_Pnt p3(0.183, -0.683, 1.0); // Point from #62
gp_Pnt p4(-0.683, -0.183, 1.0); // Point from #61
TopoDS_Edge edge1 = BRepBuilderAPI_MakeEdge(p1, p2); // Edge from point #55 to #56
TopoDS_Edge edge2 = BRepBuilderAPI_MakeEdge(p2, p3); // Edge from point #56 to #62
TopoDS_Edge edge3 = BRepBuilderAPI_MakeEdge(p3, p4); // Edge from point #62 to #61
TopoDS_Edge edge4 = BRepBuilderAPI_MakeEdge(p4, p1); // Edge from point #61 to #55
// Create corresponding 2D curves in the parametric space (u-v space) of the B-Spline surface
gp_Pnt2d uv1(0.0, 0.0); // Parametric coordinates corresponding to p1 (u, v)
gp_Pnt2d uv2(0.0, 1.0); // Parametric coordinates corresponding to p2 (u, v)
gp_Pnt2d uv3(1.0, 1.0); // Parametric coordinates corresponding to p3 (u, v)
gp_Pnt2d uv4(1.0, 0.0); // Parametric coordinates corresponding to p4 (u, v)
Handle(Geom2d_TrimmedCurve) c2dEdge1 = new Geom2d_TrimmedCurve(new Geom2d_Line(gp_Lin2d(uv1, gp_Dir2d(uv2.XY() - uv1.XY()))), 0.0, 1.0);
Handle(Geom2d_TrimmedCurve) c2dEdge2 = new Geom2d_TrimmedCurve(new Geom2d_Line(gp_Lin2d(uv2, gp_Dir2d(uv3.XY() - uv2.XY()))), 0.0, 1.0);
Handle(Geom2d_TrimmedCurve) c2dEdge3 = new Geom2d_TrimmedCurve(new Geom2d_Line(gp_Lin2d(uv3, gp_Dir2d(uv4.XY() - uv3.XY()))), 0.0, 1.0);
Handle(Geom2d_TrimmedCurve) c2dEdge4 = new Geom2d_TrimmedCurve(new Geom2d_Line(gp_Lin2d(uv4, gp_Dir2d(uv1.XY() - uv4.XY()))), 0.0, 1.0);
// Assign the 2D curves to the edges on the B-Spline surface using the correct signature
BRep_Builder builder;
TopLoc_Location identityLocation; // No transformation (identity)
builder.UpdateEdge(edge1, c2dEdge1, bsplineSurface, identityLocation, Precision::Confusion());
builder.UpdateEdge(edge2, c2dEdge2, bsplineSurface, identityLocation, Precision::Confusion());
builder.UpdateEdge(edge3, c2dEdge3, bsplineSurface, identityLocation, Precision::Confusion());
builder.UpdateEdge(edge4, c2dEdge4, bsplineSurface, identityLocation, Precision::Confusion());
// Create a wire from the edges to represent the boundary
BRepBuilderAPI_MakeWire wireMaker;
wireMaker.Add(edge1);
wireMaker.Add(edge2);
wireMaker.Add(edge3);
wireMaker.Add(edge4);
TopoDS_Wire wire = wireMaker.Wire();
// Create a face from the B-Spline surface with the boundary wire
TopoDS_Face face = BRepBuilderAPI_MakeFace(bsplineSurface, wire);
// Optionally, update the face tolerance
builder.UpdateFace(face, 1e-6); // Set tolerance if needed
// Create a shell and add the face
TopoDS_Shell shell;
builder.MakeShell(shell);
builder.Add(shell, face);
shell.Closed(Standard_True); // Set the shell as closed
// Export to STEP file if needed
STEPControl_Writer writer;
writer.Transfer(shell, STEPControl_AsIs);
writer.Write(config.stpFile.string().c_str());
}

View File

@ -0,0 +1,12 @@
//
// Created by ofskrand on 13.09.2024.
//
#ifndef STP2GLB_BSPLINESURF_H
#define STP2GLB_BSPLINESURF_H
#include "../../config_structs.h"
void make_a_bspline_surf(const GlobalConfig& config);
#endif //STP2GLB_BSPLINESURF_H

141
src/cadit/occt/convert.cpp Normal file
View File

@ -0,0 +1,141 @@
#include "convert.h"
#include <STEPCAFControl_Reader.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <RWGltf_CafWriter.hxx>
#include <TDocStd_Document.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <filesystem>
#include <XSControl_TransferReader.hxx>
#include <Transfer_TransientProcess.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <Interface_Graph.hxx>
#include <Interface_Static.hxx>
#include <Standard_Type.hxx>
#include <map>
#include <StepData_StepModel.hxx>
#include <XSControl_WorkSession.hxx>
#include "custom_progress.h"
#include "geometry_iterator.h"
#include "step_helpers.h"
#include "step_tree.h"
#include "../../config_structs.h"
void convert_stp_to_glb(const GlobalConfig& config)
{
// Initialize the STEPCAFControl_Reader
STEPCAFControl_Reader reader;
reader.SetColorMode(true);
reader.SetNameMode(true);
reader.SetLayerMode(true);
reader.SetPropsMode(false);
reader.SetGDTMode(true);
reader.SetMatMode(true);
reader.SetViewMode(false);
// Mesh parameters
IMeshTools_Parameters meshParams;
meshParams.Angle = config.angularDeflection;
meshParams.Deflection = config.linearDeflection;
meshParams.Relative = config.relativeDeflection;
meshParams.MinSize = 0.1;
meshParams.AngleInterior = 0.5;
meshParams.DeflectionInterior = 0.1;
meshParams.CleanModel = Standard_True;
meshParams.InParallel = Standard_True;
meshParams.AllowQualityDecrease = Standard_True;
// Read the STEP file
auto start = std::chrono::high_resolution_clock::now();
std::cout << "Reading STEP file: " << config.stpFile << std::endl;
if (reader.ReadFile(config.stpFile.string().c_str()) != IFSelect_RetDone)
throw std::runtime_error("Error reading STEP file");
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration<double>(stop - start).count();
std::cout << "Read complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";
// Transfer to a document
std::cout << "Transferring data to document" << "\n";
start = std::chrono::high_resolution_clock::now();
const Handle(TDocStd_Document) doc = new TDocStd_Document("MDTV-XCAF");
Handle(CustomProgressIndicator) progress_indicator = new CustomProgressIndicator();
// Create a progress range with a default name and range
if (Message_ProgressRange progressRange = progress_indicator->Start(); !reader.Transfer(doc, progressRange))
throw std::runtime_error("Error transferring data to document");
stop = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration<double>(stop - start).count();
std::cout << "Transfer complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";
// Tessellation
const Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
TDF_LabelSequence labelSeq;
shapeTool->GetShapes(labelSeq);
std::vector<ProcessResult> failed_nodes;
std::cout << "Beginning tessellation of shapes: " << labelSeq.Length() << "\n";
start = std::chrono::high_resolution_clock::now();
for (Standard_Integer i = 1; i <= labelSeq.Length(); ++i)
{
auto label = labelSeq.Value(i);
TopoDS_Shape shape = XCAFDoc_ShapeTool::GetShape(label);
std::cout << "Tessellating shape " << i << " of " << labelSeq.Length() << std::endl;
if (!perform_tessellation_with_timeout(shape, meshParams, config.tessellation_timout, progress_indicator)) {
std::cout << "Tessellation timed out.\n";
// get entity from shape
auto result = ProcessResult();
result.added_to_model = false;
result.geometryIndex = i;
result.skip_reason = "Tessellation timed out";
failed_nodes.push_back(result);
}
}
stop = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration<double>(stop - start).count();
std::cout << "Tessellation complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";
// Write to GLB
std:: cout << "Writing to GLB file: " << config.glbFile << "\n";
start = std::chrono::high_resolution_clock::now();
RWGltf_CafWriter writer(config.glbFile.c_str(), true); // true for binary format
// Additional file information (can be empty if not needed)
const TColStd_IndexedDataMapOfStringString file_info;
// Progress indicator (can be null if progress tracking is not needed)
const Message_ProgressRange progress;
// if output parent directory is != "" and does not exist, create it
if (const std::filesystem::path glb_dir = config.glbFile.parent_path(); !glb_dir.empty() && !exists(glb_dir))
{
create_directories(glb_dir);
}
if (!writer.Perform(doc, file_info, progress))
{
throw std::runtime_error("Error writing GLB file");
}
stop = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration<double>(stop - start).count();
std::cout << "Write complete in " << std::fixed << std::setprecision(2) << duration << " seconds" << "\n";
// iterate over all nodes that weren't added to the model and save the list to json
const std::filesystem::path out_json_log_file = config.glbFile.parent_path() / config.glbFile.stem().concat(
"-log.json");
std::ofstream log_file(out_json_log_file);
log_file << "[\n";
for (const auto &result: failed_nodes) {
log_file << "{\n";
log_file << "\"geometryIndex\": " << result.geometryIndex << ",\n";
log_file << R"("skipReason": ")" << result.skip_reason << "\"\n";
log_file << "},\n";
}
log_file << "]\n";
log_file.close();
}

13
src/cadit/occt/convert.h Normal file
View File

@ -0,0 +1,13 @@
//
// Created by ofskrand on 12.09.2024.
//
#ifndef STEP_TO_GLB_V1_H
#define STEP_TO_GLB_V1_H
#include "../../config_structs.h"
// Function to convert STEP file to GLB file
void convert_stp_to_glb(const GlobalConfig& config);
#endif //STEP_TO_GLB_V1_H

View File

@ -0,0 +1,80 @@
#include "custom_progress.h"
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>
CustomProgressIndicator::CustomProgressIndicator() : shouldCancel(false), progress(0.0), lastProgress(-1.0) {
// Start the progress update thread
progressThread = std::thread(&CustomProgressIndicator::UpdateProgress, this);
}
CustomProgressIndicator::~CustomProgressIndicator() {
// Stop the progress update thread safely
if (progressThread.joinable()) {
shouldCancel = true;
progressThread.join();
}
}
Standard_Boolean CustomProgressIndicator::UserBreak() {
return shouldCancel; // Return true to stop the operation
}
void CustomProgressIndicator::Show(const Message_ProgressScope &theScope, const Standard_Boolean isForce) {
// This function will be used to set progress from your existing logic
const double newProgress = GetPosition(); // Get current progress [0.0, 1.0]
SetProgress(newProgress); // Update progress asynchronously
}
void CustomProgressIndicator::Cancel() {
shouldCancel = true;
}
void CustomProgressIndicator::Reset() {
shouldCancel = false;
SetProgress(0.0); // Reset progress to 0
}
void CustomProgressIndicator::SetProgress(double newProgress) {
std::lock_guard<std::mutex> lock(mtx);
progress = newProgress;
}
void CustomProgressIndicator::UpdateProgress() {
while (!shouldCancel) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Update progress every 100ms
// Lock the mutex to safely access the progress value
std::lock_guard<std::mutex> lock(mtx);
// Only update the progress if it has changed by at least 5%
if (static_cast<int>(progress * 100) / 5 > static_cast<int>(lastProgress * 100) / 5) {
lastProgress = progress;
const int barWidth = 50; // Width of the progress bar
const int pos = static_cast<int>(progress * barWidth);
// Lock std::cout for thread safety to avoid race conditions
std::lock_guard<std::mutex> coutLock(coutMutex);
// Clear the line before printing the new progress
std::cout << "\r" << std::string(80, ' '); // Clear the current line (adjust the size if necessary)
std::cout << "\r[";
// Build the progress bar
for (int i = 0; i < barWidth; ++i) {
if (i < pos) std::cout << "="; // Completed part
else if (i == pos) std::cout << ">"; // Current progress
else std::cout << " "; // Remaining part
}
std::cout << "] " << static_cast<int>(progress * 100.0) << "% " << std::flush;
}
}
// Finalize the progress bar when the task is done
std::lock_guard<std::mutex> coutLock(coutMutex);
std::cout << "\r[==================================================] 100%" << std::endl;
}

View File

@ -0,0 +1,52 @@
#ifndef CUSTOM_PROGRESS_H
#define CUSTOM_PROGRESS_H
#include <Message_ProgressIndicator.hxx>
#include <atomic>
#include <thread>
#include <mutex>
class CustomProgressIndicator : public Message_ProgressIndicator {
public:
CustomProgressIndicator(); // Constructor
~CustomProgressIndicator(); // Destructor
//! Called by OpenCASCADE to check if the operation should stop
Standard_Boolean UserBreak() override;
//! Updates progress (called by OpenCASCADE)
void Show(const Message_ProgressScope &theScope, const Standard_Boolean isForce) override;
//! Cancels the progress update (stops background thread)
void Cancel();
//! Resets the progress indicator
void Reset() override;
private:
// Thread-safe method to set progress
void SetProgress(double newProgress);
// Background thread function to update progress asynchronously
void UpdateProgress();
// Flag to indicate whether the operation should be canceled
std::atomic<bool> shouldCancel;
// Current progress (from 0.0 to 1.0)
double progress;
// Last progress to track the threshold for updating
double lastProgress;
// Mutex to protect shared progress data between threads
std::mutex mtx;
// Mutex for locking std::cout to prevent race conditions in printing
std::mutex coutMutex;
// Background thread for asynchronous progress updates
std::thread progressThread;
};
#endif // CUSTOM_PROGRESS_H

239
src/cadit/occt/debug.cpp Normal file
View File

@ -0,0 +1,239 @@
#include <STEPCAFControl_Reader.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <RWGltf_CafWriter.hxx>
#include <TDocStd_Document.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <execution>
#include <filesystem>
#include <XCAFDoc_ShapeTool.hxx>
#include <StepData_StepModel.hxx>
#include <Interface_EntityIterator.hxx>
#include <BRepBuilderAPI_MakeShape.hxx>
#include "geometry_iterator.h"
#include <iostream>
#include "debug.h"
#include <future>
#include "step_writer.h"
#include <Interface_Static.hxx>
#include <Interface_Graph.hxx>
#include <StepBasic_Product.hxx>
#include "custom_progress.h"
#include "helpers.h"
#include "step_helpers.h"
#include "step_tree.h"
#include "../../config_structs.h"
bool should_process_geometry(const Handle(Standard_Transient) &brep, const ProductNode &node,
const GlobalConfig &config) {
if (!brep->IsKind(STANDARD_TYPE(StepShape_SolidModel))) {
if (config.solidOnly) {
return false;
}
}
if (!config.filter_names_include.empty()) {
if (!check_if_string_in_vector(config.filter_names_include, node.name)) {
return false;
}
}
if (!config.filter_names_exclude.empty()) {
if (check_if_string_in_vector(config.filter_names_exclude, node.name)) {
return false;
}
}
return true;
}
void debug_stp_to_glb(const GlobalConfig &config) {
// Initialize the STEPCAFControl_Reader
STEPCAFControl_Reader reader;
Interface_Static::SetIVal("FromSTEP.FixShape.FixShellOrientationMode", 0);
Interface_Static::SetIVal("read.step.shape.repair.mode", 0);
Interface_Static::SetIVal("read.precision.mode", 0);
// Set reader parameters
StepData_ConfParameters params;
params.ReadProps = false;
params.ReadRelationship = true;
params.ReadLayer = true;
params.ReadAllShapes = true;
params.ReadName = true;
params.ReadSubshapeNames = true;
params.ReadResourceName = true;
params.ReadColor = true;
params.ReadPrecisionMode = StepData_ConfParameters::ReadMode_Precision_User;
params.ReadPrecisionVal = 1;
params.ReadNonmanifold = true;
// Mesh parameters
IMeshTools_Parameters meshParams;
meshParams.Angle = config.angularDeflection;
meshParams.Deflection = config.linearDeflection;
meshParams.Relative = config.relativeDeflection;
meshParams.MinSize = 0.1;
meshParams.AngleInterior = 0.5;
meshParams.DeflectionInterior = 0.1;
meshParams.CleanModel = Standard_True;
meshParams.InParallel = Standard_True;
meshParams.AllowQualityDecrease = Standard_True;
{
TIME_BLOCK("Reading STEP file");
if (reader.ReadFile(config.stpFile.string().c_str(), params) != IFSelect_RetDone)
throw std::runtime_error("Error reading STEP file");
}
Interface_Static::SetIVal("FromSTEP.FixShape.FixShellOrientationMode", 0);
auto num_roots = reader.NbRootsForTransfer();
std::cout << "Number of roots for transfer: " << num_roots << "\n";
auto default_reader = reader.ChangeReader();
auto model = default_reader.StepModel();
// Build the graph of references
Interface_Graph theGraph(model, /*keepTransient*/ Standard_False);
auto iterator = model->Entities();
auto num_entities = iterator.NbEntities();
std::cout << "Number of entities: " << num_entities << "\n";
// Extract hierarchy
auto roots = ExtractProductHierarchy(model, theGraph);
add_geometries_to_nodes(roots, theGraph);
auto step_store = StepStore(roots);
// Convert Hierarchy to JSON
std::string jsonOutput = ExportHierarchyToJson(roots);
// Then write to file or print to console:
const std::filesystem::path out_json_file = config.glbFile.parent_path() / config.glbFile.stem().concat(
"-hierarchy.json");
std::ofstream file(out_json_file);
file << jsonOutput;
file.close();
std::cout << "Hierarchy exported to assembly_hierarchy.json\n";
int num_geometry = 0;
int num_products = 0;
// first find the number of geometries
for (const auto &node: GeometryRange(roots)) {
num_geometry += node.geometryInstances.size();
num_products++;
}
auto curr_shape = 0;
auto curr_product = 0;
// Create a custom progress indicator in a separate thread
const Handle(CustomProgressIndicator) progress = new CustomProgressIndicator();
Handle(XCAFDoc_ColorTool) colorTool = XCAFDoc_DocumentTool::ColorTool(step_store.doc_->Main());
// Iterate over all nodes with geometry indices
for (const auto &node: GeometryRange(roots)) {
Handle(StepBasic_Product) product = Handle(StepBasic_Product)::DownCast(model->Entity(node.entityIndex));
std::cout << "Node: " << node.name << " (" << curr_product << "/" << num_products << ")"
<< ", EntityIndex: " << node.entityIndex
<< ", Geometry count: " << node.geometryInstances.size() << '\n';
for (const GeometryInstance geometry_instance: node.geometryInstances) {
std::cout << "Geometry: " << geometry_instance.entityIndex << " (" << curr_shape << "/" << num_geometry << ")\n";
auto geometry = model->Entity(geometry_instance.entityIndex);
if (!should_process_geometry(geometry, node, config)) {
std::cout << "Skipping shape: " << node.name << " (Entity: " << node.entityIndex << ")\n";
node.processResult.added_to_model = false;
node.processResult.geometryIndex = geometry_instance.entityIndex;
node.processResult.skip_reason = "Skipped by filter";
curr_shape++;
continue;
}
if (!default_reader.TransferEntity(geometry)) {
std::cerr << "Error transferring entity" << "\n";
node.processResult.added_to_model = false;
node.processResult.geometryIndex = geometry_instance.entityIndex;
node.processResult.skip_reason = "Error transferring entity";
curr_shape++;
continue;
};
TopoDS_Shape shape = default_reader.Shape(default_reader.NbShapes());
if (shape.IsNull()) {
std::cerr << "Error converting entity to shape" << "\n";
node.processResult.added_to_model = false;
node.processResult.geometryIndex = geometry_instance.entityIndex;
node.processResult.skip_reason = "Unable to convert entity to shape";
curr_shape++;
continue;
}
Quantity_Color occ_color;
const Standard_Boolean hasColor = colorTool->GetColor(shape, XCAFDoc_ColorSurf, occ_color);
Color color;
if (!hasColor) {
color = random_color();
} else {
color = Color(occ_color.Red(), occ_color.Green(), occ_color.Blue());
}
std::cout << "Adding Shape: " << node.name << " (Entity: " << node.entityIndex << ") to STEP Writer\n";
step_store.add_shape(shape, node.name, color, node);
// Updated code block
{
TIME_BLOCK("Applying tessellation");
if (!perform_tessellation_with_timeout(shape, meshParams, config.tessellation_timout, progress)) {
std::cout << "Tessellation timed out.\n";
node.processResult.added_to_model = false;
node.processResult.geometryIndex = geometry_instance.entityIndex;
node.processResult.skip_reason = "Tessellation timed out";
curr_shape++;
continue;
}
}
curr_shape++;
}
if (config.max_geometry_num != 0 && curr_shape >= config.max_geometry_num) {
break;
}
curr_product++;
}
step_store.to_glb(config.glbFile);
const std::filesystem::path out_file = config.glbFile.parent_path() / config.glbFile.stem().concat("-debug.stp");
step_store.to_step(out_file.string().c_str());
// iterate over all nodes that werent added to the model and save the list to json
const std::filesystem::path out_json_log_file = config.glbFile.parent_path() / config.glbFile.stem().concat(
"-log.json");
std::ofstream log_file(out_json_log_file);
log_file << "[\n";
for (const auto &node: GeometryRange(roots)) {
if (!node.processResult.added_to_model && node.processResult.geometryIndex != 0) {
log_file << "{\n";
log_file << R"("name": ")" << node.name << "\",\n";
log_file << "\"entityIndex\": " << node.entityIndex << ",\n";
log_file << "\"geometryIndex\": " << node.processResult.geometryIndex << ",\n";
log_file << R"("skipReason": ")" << node.processResult.skip_reason << "\"\n";
log_file << "},\n";
}
}
log_file << "]\n";
log_file.close();
}

13
src/cadit/occt/debug.h Normal file
View File

@ -0,0 +1,13 @@
//
// Created by ofskrand on 23.01.2024.
//
#ifndef STEP_TO_GLB_H
#define STEP_TO_GLB_H
#include "../../config_structs.h"
// Function to convert STEP file to GLB file
void debug_stp_to_glb(const GlobalConfig& config);
#endif //STEP_TO_GLB_H

View File

@ -0,0 +1,137 @@
#ifndef GEOMETRY_ITERATOR_HPP
#define GEOMETRY_ITERATOR_HPP
#include <stack>
#include <utility>
#include <iterator>
#include "step_tree.h"
class GeometryIterator {
public:
using value_type = const ProductNode;
using pointer = const ProductNode*;
using reference = const ProductNode&;
using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t;
// Constructor: Initialize with a pointer to the root node
explicit GeometryIterator(pointer root) {
if (root) {
stack.emplace(root, 0);
advanceToNextValid();
}
}
// Default constructor for end iterator
GeometryIterator() = default;
// Dereference operator
reference operator*() const {
return *stack.top().first;
}
// Arrow operator
pointer operator->() const {
return stack.top().first;
}
// Pre-increment
GeometryIterator& operator++() {
stack.top().second++;
advanceToNextValid();
return *this;
}
// Post-increment
GeometryIterator operator++(int) {
GeometryIterator temp = *this;
++(*this);
return temp;
}
// Equality
bool operator==(const GeometryIterator& other) const {
return stack == other.stack;
}
// Inequality
bool operator!=(const GeometryIterator& other) const {
return !(*this == other);
}
private:
// Each stack entry: (currentNode, currentChildIndex)
std::stack<std::pair<pointer, size_t>> stack;
// Advance to the next node that has geometryIndices, or run out
void advanceToNextValid() {
while (!stack.empty()) {
auto& [currentNode, childIndex] = stack.top();
// 1) If this is the first time we see this node (childIndex == 0)
// and it has geometry, we stop here so operator* sees it.
if (childIndex == 0 && !currentNode->geometryInstances.empty()) {
return;
}
// 2) Otherwise, if we still have children to explore
if (childIndex < currentNode->children.size()) {
// Get the raw pointer from the unique_ptr
auto* childPtr = currentNode->children[childIndex].get();
stack.emplace(childPtr, 0);
childIndex++;
}
else {
// No more children -> pop
stack.pop();
}
}
}
};
// Helper functions for iterating a single ProductNode
inline GeometryIterator begin(const ProductNode& root) {
return GeometryIterator(&root);
}
inline GeometryIterator end(const ProductNode&) {
return {};
}
class GeometryRange {
public:
// Suppose we have a vector of unique_ptr<ProductNode> as roots
explicit GeometryRange(const std::vector<std::unique_ptr<ProductNode>>& roots) {
for (auto const& rootPtr : roots) {
// rootPtr is a std::unique_ptr<ProductNode>, so we pass rootPtr.get()
iterators.emplace_back(GeometryIterator(rootPtr.get()), GeometryIterator());
}
updateCurrentIterator();
}
GeometryIterator begin() {
return currentBegin;
}
static GeometryIterator end() {
return {};
}
private:
std::vector<std::pair<GeometryIterator, GeometryIterator>> iterators;
GeometryIterator currentBegin;
void updateCurrentIterator() {
// Find the first non-empty iterator
for (auto& [it, endIt] : iterators) {
if (it != endIt) {
currentBegin = it;
return;
}
}
// If all are empty, we produce an end iterator
currentBegin = GeometryIterator();
}
};
#endif // GEOMETRY_ITERATOR_HPP

View File

@ -0,0 +1,94 @@
//
// Created by Kristoffer on 07/05/2023.
//
#include <vector>
#include <filesystem>
#include <Message_ProgressRange.hxx>
#include <RWGltf_CafWriter.hxx>
#include <RWGltf_WriterTrsfFormat.hxx>
#include <TCollection_AsciiString.hxx>
#include <TCollection_ExtendedString.hxx>
#include <TColStd_IndexedDataMapOfStringString.hxx>
#include <TDocStd_Document.hxx>
#include <TopoDS_Compound.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include "../../geom/OccShape.h"
#include "helpers.h"
enum class Units {
M,
MM,
};
void to_gltf_from_shapes(
const std::filesystem::path& gltf_file,
const std::vector<OccShape>& occ_shape_iterable,
Units export_units = Units::M,
Units source_units = Units::M
) {
Handle(TDocStd_Document) doc = new TDocStd_Document(TCollection_ExtendedString("ada-py"));
Handle(XCAFDoc_ShapeTool) shape_tool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
Handle(XCAFDoc_ColorTool) color_tool = XCAFDoc_DocumentTool::ColorTool(doc->Main());
int i = 1;
for (const auto& step_shape : occ_shape_iterable) {
TopoDS_Shape shp = step_shape.shape;
if (shp.ShapeType() == TopAbs_COMPOUND) {
continue;
}
// Mesh mesh = tessellate_shape(shp, true, 1.0, false);
TDF_Label sub_shape_label = shape_tool->AddShape(shp);
set_color(sub_shape_label, step_shape.color, color_tool);
set_name(sub_shape_label, step_shape.name);
i++;
}
RWGltf_WriterTrsfFormat a_format = RWGltf_WriterTrsfFormat_Compact;
TColStd_IndexedDataMapOfStringString a_file_info;
a_file_info.Add(TCollection_AsciiString("Authors"), TCollection_AsciiString("ada-py"));
bool binary = gltf_file.extension() == ".glb";
RWGltf_CafWriter glb_writer(TCollection_AsciiString(gltf_file.string().c_str()), binary);
if (export_units == Units::M && source_units == Units::MM) {
glb_writer.ChangeCoordinateSystemConverter().SetInputLengthUnit(0.001);
} else if (export_units == Units::MM && source_units == Units::M) {
glb_writer.ChangeCoordinateSystemConverter().SetInputLengthUnit(1000);
}
glb_writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(RWMesh_CoordinateSystem_Zup);
glb_writer.SetTransformationFormat(a_format);
Message_ProgressRange pr;
glb_writer.Perform(doc, a_file_info, pr);
}
void to_glb_from_doc(const std::filesystem::path& glb_file, const Handle(TDocStd_Document)& doc) {
RWGltf_CafWriter writer(glb_file.c_str(), true); // true for binary format
// Additional file information (can be empty if not needed)
const TColStd_IndexedDataMapOfStringString file_info;
// Progress indicator (can be null if progress tracking is not needed)
const Message_ProgressRange progress;
// if output parent directory is != "" and does not exist, create it
if (const std::filesystem::path glb_dir = glb_file.parent_path(); !glb_dir.empty() && !exists(glb_dir))
{
create_directories(glb_dir);
}
if (!writer.Perform(doc, file_info, progress))
{
throw std::runtime_error("Error writing GLB file");
}
}

View File

@ -0,0 +1,15 @@
//
// Created by Kristoffer on 07/05/2023.
//
#ifndef NANO_OCCT_GLTF_WRITER_H
#define NANO_OCCT_GLTF_WRITER_H
#include <filesystem>
#include <Standard_Handle.hxx>
#include <TDocStd_Document.hxx>
void to_glb_from_doc(const std::filesystem::path& glb_file, const Handle(TDocStd_Document)& doc);
#endif //NANO_OCCT_GLTF_WRITER_H

103
src/cadit/occt/helpers.cpp Normal file
View File

@ -0,0 +1,103 @@
#include <vector>
#include <random>
#include <gp_XYZ.hxx>
#include <gp_Pnt.hxx>
#include <TopoDS_Solid.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <TDF_Label.hxx>
#include <TDataStd_Name.hxx>
#include <Quantity_Color.hxx>
#include <XCAFDoc_ColorType.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <optional>
#include "../../geom/Color.h"
#include "helpers.h"
#include <chrono>
TopoDS_Solid create_box(const std::vector<float>& box_origin, const std::vector<float>& box_dims)
{
gp_Pnt aBoxOrigin(box_origin[0], box_origin[1], box_origin[2]);
gp_XYZ aBoxDims(box_dims[0], box_dims[1], box_dims[2]);
return BRepPrimAPI_MakeBox(aBoxOrigin, aBoxDims.X(), aBoxDims.Y(), aBoxDims.Z());
}
// a function that returns a random tuple std::tuple<double, double, double>
Color random_color()
{
static std::mt19937 generator(std::random_device{}());
static std::uniform_real_distribution<float> distribution(0.0, 1.0);
return Color(distribution(generator), distribution(generator), distribution(generator), 1.0);
}
void set_name(const TDF_Label& label, const std::optional<std::string>& name)
{
if (!name)
{
return;
}
TCollection_ExtendedString ext_name(name->c_str());
TDataStd_Name::Set(label, ext_name);
}
void set_color(const TDF_Label& label, const Color& color,
const Handle(XCAFDoc_ColorTool)& tool)
{
float r = color.r;
float g = color.g;
float b = color.b;
Quantity_Color qty_color(r, g, b, Quantity_TOC_RGB);
tool->SetColor(label, qty_color, XCAFDoc_ColorType::XCAFDoc_ColorSurf);
}
// Utility function to split a string by a delimiter
std::vector<std::string> split(const std::string& input, char delimiter) {
std::vector<std::string> tokens;
std::stringstream ss(input);
std::string token;
while (std::getline(ss, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
// Utility function to strip surrounding quotes from a string
std::string strip_quotes(const std::string& input) {
if (input.size() >= 2 && input.front() == '"' && input.back() == '"') {
return input.substr(1, input.size() - 2);
}
return input;
}
bool check_if_string_in_vector(const std::vector<std::string>& vec, const std::string& str)
{
return std::any_of(vec.begin(), vec.end(), [&str](const std::string& filter_name)
{
return std::equal(str.begin(), str.end(),
filter_name.begin(), filter_name.end(),
[](char a, char b)
{
return std::tolower(static_cast<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
});
});
}
TimingContext::TimingContext(std::string name)
: label(std::move(name)), start(std::chrono::high_resolution_clock::now())
{
std::cout << "Starting: " << label << std::endl;
}
TimingContext::~TimingContext()
{
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration<double>(stop - start).count();
std::cout << label << " took " << std::fixed << std::setprecision(2)
<< duration << " seconds." << std::endl;
}
#define TIME_BLOCK(name) TimingContext timer_##__LINE__(name)

44
src/cadit/occt/helpers.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef NANO_OCCT_HELPERS_H
#define NANO_OCCT_HELPERS_H
#include <vector>
#include <TopoDS_Solid.hxx>
#include <TDF_Label.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <optional>
#include "../../geom/Color.h"
#include <string>
#include <chrono>
std::string strip_quotes(const std::string& input);
std::vector<std::string> split(const std::string& input, char delimiter);
bool check_if_string_in_vector(const std::vector<std::string> &vec, const std::string &str);
TopoDS_Solid create_box(const std::vector<float> &box_origin, const std::vector<float> &box_dims);
Color random_color();
void set_name(const TDF_Label &label, const std::optional<std::string> &name);
void set_color(const TDF_Label &label, const Color &color,
const Handle(XCAFDoc_ColorTool) &tool);
class TimingContext
{
public:
explicit TimingContext(std::string name);
~TimingContext();
private:
std::string label;
std::chrono::time_point<std::chrono::high_resolution_clock> start;
};
// Macro to create a timing block
#define TIME_BLOCK(name) TimingContext timer_##__LINE__(name)
#endif // NANO_OCCT_HELPERS_H

View File

@ -0,0 +1,698 @@
#include <STEPCAFControl_Reader.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <RWGltf_CafWriter.hxx>
#include <TDocStd_Document.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <filesystem>
#include <XCAFDoc_ShapeTool.hxx>
#include <Interface_EntityIterator.hxx>
#include <StepShape_SolidModel.hxx>
#include <StepShape_Face.hxx>
#include <StepShape_AdvancedFace.hxx>
#include <BRepBuilderAPI_MakeShape.hxx>
#include <BRep_Builder.hxx>
#include <TDataStd_Name.hxx>
#include <iostream>
#include "step_writer.h"
#include "step_helpers.h"
#include <Interface_Graph.hxx>
#include <StepGeom_Axis2Placement3d.hxx>
#include <StepGeom_CartesianPoint.hxx>
#include <StepGeom_Direction.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <gp_Trsf.hxx>
#include <StepRepr_ShapeRepresentationRelationship.hxx>
#include <StepShape_ShapeRepresentation.hxx>
#include <StepRepr_ProductDefinitionShape.hxx>
#include <StepShape_ShapeDefinitionRepresentation.hxx>
#include <StepBasic_ProductDefinition.hxx>
#include <StepBasic_ProductDefinitionFormation.hxx>
#include <StepBasic_Product.hxx>
#include <StepRepr_Representation.hxx>
#include <TCollection_HAsciiString.hxx>
#include <thread>
#include <unordered_set>
#include "custom_progress.h"
#include "helpers.h"
Handle(Standard_Transient) get_entity_from_graph_path(const Handle(Standard_Transient)& entity,
Interface_Graph& theGraph, std::vector<std::string> path)
{
// First check if the entity is contained in the path
if (std::find(path.begin(), path.end(), entity->DynamicType()->Name()) == path.end())
{
return {};
}
// find index of entity in path
auto index = std::find(path.begin(), path.end(), entity->DynamicType()->Name());
// if entity is last item in path, return it
if (index == path.end() - 1)
{
return entity;
}
// if not last item, find the next entity in the path
auto target_type = path[index - path.begin() + 1];
auto parents = theGraph.Sharings(entity);
while (parents.More())
{
Handle(Standard_Transient) parent = parents.Value();
auto parent_name = parent->DynamicType()->Name();
if (parent_name == target_type)
{
return get_entity_from_graph_path(parent, theGraph, path);
}
}
return {};
}
std::string extractProductNameFromSDR(const Handle(StepShape_ShapeDefinitionRepresentation)& sdr)
{
if (sdr.IsNull())
{
return {};
}
// 1) sdr->Definition() returns a "StepRepr_RepresentedDefinition" (by value)
StepRepr_RepresentedDefinition def = sdr->Definition();
if (def.Value().IsNull())
{
// Nothing linked
return {};
}
// 2) The underlying handle is def.Value(). We expect it might be a StepBasic_ProductDefinition
Handle(StepRepr_ProductDefinitionShape) pds =
Handle(StepRepr_ProductDefinitionShape)::DownCast(def.Value());
if (pds.IsNull())
{
// It's not a product definition. Could be something else.
return {};
}
StepRepr_CharacterizedDefinition cd = pds->Definition();
if (cd.IsNull())
{
return {};
}
// 3) Now from the product definition, we can get the formation -> product
Handle(StepBasic_ProductDefinition) pd = cd.ProductDefinition();
if (pd.IsNull())
{
return {};
}
// 3) Now from the product definition, we can get the formation -> product
Handle(StepBasic_ProductDefinitionFormation) pdf = pd->Formation();
if (pdf.IsNull())
{
return {};
}
Handle(StepBasic_Product) product = pdf->OfProduct();
if (product.IsNull() || product->Name().IsNull())
{
return {};
}
return product->Name()->ToCString();
}
std::string getStepProductNameFromGraph(const Handle(Standard_Transient)& entity, Interface_Graph& theGraph)
{
// Relationship tree
// StepShape_ManifoldSolidBrep -> SHAPE_REPRESENTATION -> ShapeDefinitionRepresentation
// 1) Get the model index for this entity
std::vector<std::string> path = {
"StepShape_ManifoldSolidBrep", "StepShape_AdvancedBrepShapeRepresentation",
"StepShape_ShapeDefinitionRepresentation"
};
// "StepBasic_ProductDefinition", "StepBasic_ProductDefinitionFormation", "StepBasic_Product"
Handle(StepRepr_RepresentationItem) item =
Handle(StepRepr_RepresentationItem)::DownCast(entity);
auto base_type = entity->DynamicType()->Name();
auto shape_def_rep = get_entity_from_graph_path(entity, theGraph, path);
// get PRODUCT_DEFINITION_SHAPE from shape_def_rep
// get PRODUCT_DEFINITION from PRODUCT_DEFINITION_SHAPE
// get PRODUCT from PRODUCT_DEFINITION
// get NAME from PRODUCT
if (!shape_def_rep.IsNull())
{
auto productName = extractProductNameFromSDR(
Handle(StepShape_ShapeDefinitionRepresentation)::DownCast(shape_def_rep));
if (!productName.empty())
{
return productName;
}
}
return "";
}
//--------------------------------------
// Extract product name from a single entity
//--------------------------------------
std::string getStepProductName(const Handle(Standard_Transient)& entity, Interface_Graph& theGraph)
{
// ----------------------------------------------------------
// 1) StepShape_ShapeDefinitionRepresentation: might link to a ProductDefinition
// ----------------------------------------------------------
{
Handle(StepShape_ShapeDefinitionRepresentation) sdr =
Handle(StepShape_ShapeDefinitionRepresentation)::DownCast(entity);
if (!sdr.IsNull())
{
// sdr->Definition() returns a by-value StepRepr_RepresentedDefinition
StepRepr_RepresentedDefinition def = sdr->Definition();
if (!def.Value().IsNull())
{
// Try cast to StepBasic_ProductDefinition
Handle(StepBasic_ProductDefinition) pd =
Handle(StepBasic_ProductDefinition)::DownCast(def.Value());
if (!pd.IsNull())
{
// ProductDefinitionFormation -> OfProduct
if (!pd->Formation().IsNull())
{
Handle(StepBasic_Product) product = pd->Formation()->OfProduct();
if (!product.IsNull() && !product->Name().IsNull())
{
std::string result = product->Name()->ToCString();
if (!result.empty())
{
return result;
}
}
}
}
}
}
}
// ----------------------------------------------------------
// 2) StepRepr_ShapeRepresentation / StepRepr_Representation
// to see if there's a shape-level or representation-level name
// ----------------------------------------------------------
{
Handle(StepShape_ShapeRepresentation) shapeRepr =
Handle(StepShape_ShapeRepresentation)::DownCast(entity);
if (!shapeRepr.IsNull())
{
// shapeRepr inherits from StepRepr_Representation, which has a Name()
if (!shapeRepr->Name().IsNull())
{
std::string result = shapeRepr->Name()->ToCString();
if (!result.empty())
{
return result;
}
}
}
}
{
// Maybe it's a plain StepRepr_Representation
Handle(StepRepr_Representation) repr =
Handle(StepRepr_Representation)::DownCast(entity);
if (!repr.IsNull())
{
if (!repr->Name().IsNull())
{
std::string result = repr->Name()->ToCString();
if (!result.empty())
{
return result;
}
}
}
}
// ----------------------------------------------------------
// 3) StepRepr_RepresentationItem (like faces, edges, etc.)
// ----------------------------------------------------------
{
Handle(StepRepr_RepresentationItem) item =
Handle(StepRepr_RepresentationItem)::DownCast(entity);
if (!item.IsNull())
{
if (!item->Name().IsNull())
{
std::string result = item->Name()->ToCString();
if (!result.empty())
{
return result;
}
}
}
}
// If everything fails or is empty, we return empty string
return {};
}
void update_location(TopoDS_Shape& shape)
{
TopLoc_Location loc = shape.Location();
// Extract translation components from the transformation matrix
gp_XYZ translation = loc.Transformation().TranslationPart();
Standard_Real x = translation.X();
Standard_Real y = translation.Y();
Standard_Real z = translation.Z();
// Output the coordinates
std::cout << "X: " << x << ", Y: " << y << ", Z: " << z << "\n";
if (!loc.IsIdentity())
{
shape.Location(loc); // Apply the transformation
}
}
gp_Trsf get_product_transform(TopoDS_Shape& shape, const Handle(StepBasic_Product)& product)
{
gp_Trsf transform;
// Helper lambda to extract gp_Trsf from Axis2Placement3D
auto extract_transform_from_placement = [](const Handle(StepGeom_Axis2Placement3d)& placement) -> gp_Trsf
{
gp_Trsf trsf;
if (!placement.IsNull())
{
gp_Pnt location(
placement->Location()->CoordinatesValue(1),
placement->Location()->CoordinatesValue(2),
placement->Location()->CoordinatesValue(3));
gp_Dir zDir(
placement->Axis()->DirectionRatios()->Value(1),
placement->Axis()->DirectionRatios()->Value(2),
placement->Axis()->DirectionRatios()->Value(3));
gp_Dir xDir(
placement->RefDirection()->DirectionRatios()->Value(1),
placement->RefDirection()->DirectionRatios()->Value(2),
placement->RefDirection()->DirectionRatios()->Value(3));
gp_Ax3 ax3(location, zDir, xDir);
trsf.SetTransformation(ax3);
}
return trsf;
};
// Traverse the STEP structure to accumulate transformations
Handle(StepRepr_ProductDefinitionShape) prodDefShape;
// Get FrameOfReference from Product
Handle(StepRepr_Representation) representation;
if (product->NbFrameOfReference() > 0)
{
// Assuming FrameOfReference() returns a list of StepBasic_ProductContext
const auto& frameOfReference = product->FrameOfReference();
for (Standard_Integer i = 1; i <= frameOfReference->Length(); ++i)
{
Handle(StepBasic_ProductContext) productContext = frameOfReference->Value(i);
if (!productContext.IsNull())
{
// Retrieve the Representation (if applicable)
Handle(StepRepr_Representation) rep =
Handle(StepRepr_Representation)::DownCast(productContext);
if (!rep.IsNull())
{
representation = rep;
break; // Use the first valid representation
}
}
}
}
if (!representation.IsNull())
{
// Iterate over items in the representation to find transformations
for (Standard_Integer i = 1; i <= representation->NbItems(); ++i)
{
Handle(Standard_Transient) item = representation->ItemsValue(i);
// If the item is a ShapeRepresentation, get its transformation
if (item->IsKind(STANDARD_TYPE(StepShape_ShapeRepresentation)))
{
Handle(StepShape_ShapeRepresentation) shapeRep =
Handle(StepShape_ShapeRepresentation)::DownCast(item);
for (Standard_Integer j = 1; j <= shapeRep->NbItems(); ++j)
{
Handle(Standard_Transient) subItem = shapeRep->ItemsValue(j);
if (subItem->IsKind(STANDARD_TYPE(StepGeom_Axis2Placement3d)))
{
Handle(StepGeom_Axis2Placement3d) axisPlacement =
Handle(StepGeom_Axis2Placement3d)::DownCast(subItem);
transform = transform * extract_transform_from_placement(axisPlacement);
}
}
}
}
}
// Apply the accumulated transform to the shape
{
BRepBuilderAPI_Transform shapeTransform(transform);
}
return transform;
}
std::string get_name(const Handle(StepRepr_RepresentationItem)& repr_item)
{
if (!repr_item.IsNull())
{
auto name = repr_item->Name()->ToCString();
if (name)
{
return name;
}
return "Unnamed";
}
return {};
}
TopoDS_Shape make_shape(const Handle(StepShape_SolidModel)& solid_model, STEPControl_Reader& reader)
{
if (!solid_model.IsNull())
{
TIME_BLOCK("Transferring solid model entity");
// Convert the solid model into an OpenCascade shape
if (!reader.TransferEntity(solid_model))
{
std::cerr << "Error transferring entity" << std::endl;
};
TopoDS_Shape shape = reader.Shape(reader.NbShapes());
return shape;
}
return {};
}
TopoDS_Shape make_shape(const Handle(StepShape_Face)& face, STEPControl_Reader& reader)
{
if (!face.IsNull())
{
TIME_BLOCK("Transferring face entity");
if (!reader.TransferEntity(face))
{
std::cerr << "Error transferring face entity" << std::endl;
}
TopoDS_Shape shape = reader.Shape(reader.NbShapes());
return shape;
}
return {};
}
TDF_Label add_shape_to_document(const TopoDS_Shape& shape, const std::string& name,
const Handle(XCAFDoc_ShapeTool)& shape_tool, IMeshTools_Parameters& meshParams)
{
TDF_Label shape_label;
if (!shape.IsNull())
{
{
TIME_BLOCK("Applying tessellation");
// Perform tessellation (discretization) on the shape
BRepMesh_IncrementalMesh mesh(shape, meshParams); // Adjust 0.1 for finer/coarser tessellation
}
// Add the TopoDS_Shape to the document
{
TIME_BLOCK("Adding shape '" + name + "' to document\n");
shape_label = shape_tool->AddShape(shape);
}
}
return shape_label;
}
// Constructor
ConvertObject::ConvertObject(const std::string& name, const TopoDS_Shape& shape, TDF_Label& shape_label,
bool addedToModel)
: name(name), shape(shape), shape_label(shape_label), AddedToModel(addedToModel)
{
}
// Destructor (optional if needed)
ConvertObject::~ConvertObject()
{
// Clean up resources if necessary
}
// Custom filter function (example: filter by entity type or name)
bool CustomFilter(const Handle(Standard_Transient)& entity)
{
if (entity->IsKind(STANDARD_TYPE(StepBasic_ProductDefinition)))
{
return true; // Matches filter
}
return false;
}
Interface_EntityIterator MyTypedExpansions(const Handle(Standard_Transient)& rootEntity,
const Handle(Standard_Type)& targetType,
const Interface_Graph& theGraph)
{
// Prepare an empty sequence to accumulate matching entities
Handle(TColStd_HSequenceOfTransient) matchedEntities =
new TColStd_HSequenceOfTransient();
if (rootEntity.IsNull())
{
// Return an empty iterator
return {matchedEntities};
}
// We'll need the model to get entity indices (theGraph.Model()->Number())
Handle(Interface_InterfaceModel) model = theGraph.Model();
if (model.IsNull())
{
// Return empty if no model
return {matchedEntities};
}
// BFS queue
std::queue<Handle(Standard_Transient)> toVisit;
// track visited with a set of entity indices to avoid loops
std::unordered_set<Standard_Integer> visited;
// Helper to enqueue unvisited
auto enqueueIfNotVisited = [&](const Handle(Standard_Transient)& ent)
{
if (ent.IsNull()) return;
// theGraph.Model()->Number(ent) gives 1-based index in the model
Standard_Integer idx = model->Number(ent);
// Only enqueue if it's in the model and hasn't been visited
if (idx > 0 && visited.find(idx) == visited.end())
{
visited.insert(idx);
toVisit.push(ent);
}
};
// Initialize BFS with the root
enqueueIfNotVisited(rootEntity);
// BFS loop
while (!toVisit.empty())
{
Handle(Standard_Transient) current = toVisit.front();
toVisit.pop();
// If current is or derives from targetType, record it
if (current->IsKind(targetType))
{
matchedEntities->Append(current);
}
// Get child references using Sharings(...) = "downstream" references
// (If you need “upstream,” youd use Shareds(...).)
Interface_EntityIterator sharings = theGraph.Sharings(current);
for (sharings.Start(); sharings.More(); sharings.Next())
{
enqueueIfNotVisited(sharings.Value());
}
}
// Build an Interface_EntityIterator from the matched sequence
return {matchedEntities};
}
// A BFS that visits both Sharings (downstream references) and Shareds (upstream references).
// This ensures we don't miss geometry that might only be discovered by climbing "up"
// to a higher-level entity, then going "down" again.
Interface_EntityIterator Get_Associated_SolidModel_BiDirectional(
const Handle(Standard_Transient)& rootEntity,
const Handle(Standard_Type)& targetType,
const Interface_Graph& theGraph)
{
// Where we'll store any entity matching `targetType` that we encounter.
Handle(TColStd_HSequenceOfTransient) matchedEntities = new TColStd_HSequenceOfTransient();
if (rootEntity.IsNull())
{
return {matchedEntities};
}
// We'll need the model to get entity indices.
Handle(Interface_InterfaceModel) model = theGraph.Model();
if (model.IsNull())
{
return {matchedEntities};
}
std::queue<Handle(Standard_Transient)> toVisit;
std::unordered_set<Standard_Integer> visited; // track visited by their 1-based model index
// Helper to enqueue an entity if not already visited
auto enqueueIfNotVisited = [&](const Handle(Standard_Transient)& ent)
{
if (!ent.IsNull())
{
Standard_Integer idx = model->Number(ent);
if (idx > 0 && visited.find(idx) == visited.end())
{
visited.insert(idx);
toVisit.push(ent);
}
}
};
// Start BFS from the root
enqueueIfNotVisited(rootEntity);
// BFS loop
while (!toVisit.empty())
{
Handle(Standard_Transient) current = toVisit.front();
toVisit.pop();
// Gather children (Sharings): "downstream" references
{
Interface_EntityIterator childIter = theGraph.Sharings(current);
for (childIter.Start(); childIter.More(); childIter.Next())
{
enqueueIfNotVisited(childIter.Value());
}
}
// Gather parents (Shareds): "upstream" references
{
// Traverse SHAPE_REPRESENTATION_RELATIONSHIP to follow links
if (current->IsKind(STANDARD_TYPE(StepRepr_ShapeRepresentationRelationship)))
{
Handle(StepRepr_ShapeRepresentationRelationship) relationship =
Handle(StepRepr_ShapeRepresentationRelationship)::DownCast(current);
auto relatedRep2 = relationship->Rep2(); // Second representation
enqueueIfNotVisited(relatedRep2);
}
// if iskind product, skip this
else if (current->IsKind(STANDARD_TYPE(StepShape_ShapeDefinitionRepresentation)))
{
Handle(StepShape_ShapeDefinitionRepresentation) item =
Handle(StepShape_ShapeDefinitionRepresentation)::DownCast(current);
auto rest = item->UsedRepresentation();
enqueueIfNotVisited(rest);
}
else if (current->IsKind(STANDARD_TYPE(StepShape_ShapeRepresentation)))
{
Handle(StepShape_ShapeRepresentation) shapeRep =
Handle(StepShape_ShapeRepresentation)::DownCast(current);
auto items = shapeRep->Items();
if (!items.IsNull())
{
Standard_Integer nbItems = items->Length();
for (Standard_Integer i = 1; i <= nbItems; i++)
{
// Each element is a StepRepr_RepresentationItem
Handle(StepRepr_RepresentationItem) repItem = items->Value(i);
// Check if it is a solid model or another geometric representation
if (repItem->IsKind(STANDARD_TYPE(StepShape_SolidModel)))
{
Handle(StepShape_SolidModel) solidModel =
Handle(StepShape_SolidModel)::DownCast(repItem);
// append only if solidmodel is not already added. Can contain many geometries
if (visited.find(model->Number(solidModel)) == visited.end())
{
visited.insert(model->Number(solidModel));
matchedEntities->Append(solidModel);
}
}
}
}
}
else
{
}
}
}
// Now build an iterator from the matched sequence
return {matchedEntities};
}
// Function to perform tessellation with a timeout
bool perform_tessellation_with_timeout(const TopoDS_Shape &shape, const IMeshTools_Parameters &meshParams,
const int timeoutSeconds, const Handle(CustomProgressIndicator) &progress) {
// Create a progress range with a default name and range
const Message_ProgressRange progressRange = progress->Start();
// Flag to track if the operation timed out
std::atomic tessellationComplete = false;
// Launch tessellation in a separate thread
std::thread tessellationThread([&]() {
try {
// Run tessellation with progress monitoring
BRepMesh_IncrementalMesh mesh(shape, meshParams, progressRange);
// Mark as complete
tessellationComplete = true;
} catch (const Standard_Failure &e) {
std::cerr << "Tessellation failed: " << e.GetMessageString() << "\n";
}
});
// Monitor the tessellation thread for timeout
const auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < std::chrono::seconds(timeoutSeconds)) {
if (tessellationComplete) {
// Tessellation finished successfully
std::cout << "Tessellation completed successfully.\n";
tessellationThread.join();
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Poll every 50 ms
}
// Timeout occurred, cancel tessellation
progress->Cancel();
if (tessellationThread.joinable()) {
tessellationThread.join();
}
return false; // Tessellation timed out
}

View File

@ -0,0 +1,67 @@
//
// Created by ofskrand on 07.01.2025.
//
#ifndef STEP_HELPERS_H
#define STEP_HELPERS_H
#include <STEPCAFControl_Reader.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <StepBasic_Product.hxx>
#include <Interface_Graph.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <StepShape_SolidModel.hxx>
#include <StepShape_Face.hxx>
#include <string>
#include <TopoDS_Shape.hxx> // Include the necessary OpenCascade header for TopoDS_Shape
#include "custom_progress.h"
std::string getStepProductName(const Handle(Standard_Transient) &entity, Interface_Graph &theGraph);
void update_location(TopoDS_Shape &shape);
std::string get_name(const Handle(StepRepr_RepresentationItem) &repr_item);
TopoDS_Shape make_shape(const Handle(StepShape_Face) &face, STEPControl_Reader &reader);
TDF_Label add_shape_to_document(const TopoDS_Shape &shape, const std::string &name,
const Handle(XCAFDoc_ShapeTool) &shape_tool, IMeshTools_Parameters &meshParams);
struct ConvertObject {
std::string name; // Name of the object
TopoDS_Shape shape; // Shape data
bool AddedToModel = false; // Indicates if the object is added to the model
TDF_Label shape_label; // Label for the shape in the document
// Constructor
ConvertObject(const std::string& name, const TopoDS_Shape& , TDF_Label& shape_label, bool addedToModel = false);
// Optional: Destructor
~ConvertObject();
};
ConvertObject entity_to_shape(const Handle(Standard_Transient) &entity,
STEPControl_Reader default_reader,
const Handle(XCAFDoc_ShapeTool) &shape_tool,
IMeshTools_Parameters &meshParams,
const bool solid_only = false);
std::string getStepProductNameFromGraph(const Handle(Standard_Transient) &entity, Interface_Graph &theGraph);
bool CustomFilter(const Handle(Standard_Transient)& entity);
Interface_EntityIterator MyTypedExpansions(const Handle(Standard_Transient)& rootEntity,
const Handle(Standard_Type)& targetType,
const Interface_Graph& theGraph);
Interface_EntityIterator Get_Associated_SolidModel_BiDirectional(
const Handle(Standard_Transient)& rootEntity,
const Handle(Standard_Type)& targetType,
const Interface_Graph& theGraph);
gp_Trsf get_product_transform(TopoDS_Shape& shape, const Handle(StepBasic_Product)& product);
bool perform_tessellation_with_timeout(const TopoDS_Shape &shape, const IMeshTools_Parameters &meshParams,
const int timeoutSeconds, const Handle(CustomProgressIndicator) &progress);
#endif //STEP_HELPERS_H

View File

@ -0,0 +1,692 @@
#include <Interface_InterfaceModel.hxx>
#include <Interface_Graph.hxx>
#include <Interface_EntityIterator.hxx>
// STEP entity classes
#include <StepBasic_Product.hxx>
#include <StepBasic_ProductDefinition.hxx>
#include <StepBasic_ProductDefinitionFormation.hxx>
#include <StepRepr_NextAssemblyUsageOccurrence.hxx>
#include <StepRepr_ProductDefinitionShape.hxx>
#include <TCollection_HAsciiString.hxx>
#include <Standard_Type.hxx>
#include <Standard_Transient.hxx>
#include <StepRepr_RepresentationRelationshipWithTransformation.hxx>
#include <StepRepr_ItemDefinedTransformation.hxx>
#include <StepRepr_Transformation.hxx>
#include <StepShape_ContextDependentShapeRepresentation.hxx>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <sstream>
#include "step_tree.h"
#include <gp_Ax1.hxx>
#include <gp_Ax3.hxx>
#include <ranges>
#include <stack>
#include <StepGeom_Axis2Placement3d.hxx>
#include <StepGeom_CartesianPoint.hxx>
#include <StepGeom_Direction.hxx>
#include "step_helpers.h"
// Function to compute the transformation matrix for a given assembly instance
gp_Trsf GetTransformationMatrix(
const Handle(StepRepr_NextAssemblyUsageOccurrence) &nauo,
const Interface_Graph &theGraph) {
gp_Trsf transformation;
// Get the Related ProductDefinition
Handle(StepBasic_ProductDefinition) relatedProdDef = nauo->RelatedProductDefinition();
if (relatedProdDef.IsNull()) {
return transformation; // Return identity transform if no related product definition
}
// Find the corresponding ProductDefinitionShape
Handle(StepRepr_ProductDefinitionShape) relatedShape;
Interface_EntityIterator refs = theGraph.Sharings(relatedProdDef);
for (refs.Start(); refs.More(); refs.Next()) {
const Handle(Standard_Transient) &refEntity = refs.Value();
if (refEntity->IsKind(STANDARD_TYPE(StepRepr_ProductDefinitionShape))) {
relatedShape = Handle(StepRepr_ProductDefinitionShape)::DownCast(refEntity);
break;
}
}
// If no ProductDefinitionShape is found, return identity transform
if (relatedShape.IsNull()) {
return transformation;
}
// Look for an Axis2Placement3D referenced by the ProductDefinitionShape
Interface_EntityIterator shapeRefs = theGraph.Sharings(relatedShape);
for (shapeRefs.Start(); shapeRefs.More(); shapeRefs.Next()) {
const Handle(Standard_Transient) refEntity = shapeRefs.Value();
if (refEntity->IsKind(STANDARD_TYPE(StepGeom_Axis2Placement3d))) {
auto axisPlacement = Handle(StepGeom_Axis2Placement3d)::DownCast(refEntity);
// Extract translation
if (!axisPlacement->Location().IsNull()) {
auto location = axisPlacement->Location();
transformation.SetTranslation(
gp_Vec(location->CoordinatesValue(1), location->CoordinatesValue(2),
location->CoordinatesValue(3)));
}
// Extract rotation
if (!axisPlacement->RefDirection().IsNull() && !axisPlacement->Axis().IsNull()) {
auto refDirection = axisPlacement->RefDirection();
auto axis = axisPlacement->Axis();
auto angle = 0.0;
transformation.SetRotation(
gp_Ax1(gp_Pnt(0, 0, 0),
gp_Dir(axis->DirectionRatiosValue(1), axis->DirectionRatiosValue(2),
axis->DirectionRatiosValue(3))),
angle);
}
break;
}
}
return transformation;
}
gp_Trsf ComputeTransformationFromAxis2Placement(const Handle(StepGeom_Axis2Placement3d) &placement) {
gp_Trsf transform;
if (!placement.IsNull()) {
// Extract translation
if (!placement->Location().IsNull()) {
const auto loc = placement->Location();
transform.SetTranslation(gp_Vec(loc->CoordinatesValue(1), loc->CoordinatesValue(2),
loc->CoordinatesValue(3)));
}
// Extract rotation from directions
if (!placement->RefDirection().IsNull() && !placement->Axis().IsNull()) {
gp_Dir xDir(placement->Axis()->DirectionRatiosValue(1),
placement->Axis()->DirectionRatiosValue(2),
placement->Axis()->DirectionRatiosValue(3));
gp_Dir zDir(placement->RefDirection()->DirectionRatiosValue(1),
placement->RefDirection()->DirectionRatiosValue(2),
placement->RefDirection()->DirectionRatiosValue(3));
gp_Ax3 localAxis(transform.TranslationPart(), zDir, xDir);
transform.SetTransformation(localAxis);
}
}
return transform;
}
gp_Trsf GetTransformFromShapeRelWithTrans(
const Handle(StepRepr_RepresentationRelationshipWithTransformation) &relWithTrans) {
gp_Trsf transformation;
auto itemDefinedTrans = relWithTrans->TransformationOperator();
if (!itemDefinedTrans.IsNull()) {
auto itemDefTrans = itemDefinedTrans.ItemDefinedTransformation();
auto trans1 = itemDefTrans->TransformItem1();
auto trans2 = itemDefTrans->TransformItem2();
if (!trans1.IsNull() && !trans2.IsNull()) {
// Get AXIS2_PLACEMENT_3D entities for parent and child
auto childPlacement = Handle(StepGeom_Axis2Placement3d)::DownCast(trans1);
auto parentPlacement = Handle(StepGeom_Axis2Placement3d)::DownCast(trans2);
// Compute transformations
if (!childPlacement.IsNull() && !parentPlacement.IsNull()) {
gp_Trsf childTransform = ComputeTransformationFromAxis2Placement(childPlacement);
gp_Trsf parentTransform = ComputeTransformationFromAxis2Placement(parentPlacement);
// Combine transformations: parent^-1 * child
parentTransform.Invert();
transformation = parentTransform * childTransform;
}
}
}
return transformation;
}
gp_Trsf GetAssemblyInstanceTransformation(
const Handle(StepRepr_NextAssemblyUsageOccurrence) &nauo,
const Interface_Graph &theGraph) {
gp_Trsf transformation;
const Interface_EntityIterator refs = theGraph.Sharings(nauo);
for (refs.Start(); refs.More(); refs.Next()) {
const auto &entity = refs.Value();
if (entity->IsKind(STANDARD_TYPE(StepRepr_ProductDefinitionShape))) {
auto pds = Handle(StepRepr_ProductDefinitionShape)::DownCast(entity);
Interface_EntityIterator pdsRefs = theGraph.Sharings(pds);
for (pdsRefs.Start(); pdsRefs.More(); pdsRefs.Next()) {
const auto &pdsRef = pdsRefs.Value();
if (pdsRef->IsKind(STANDARD_TYPE(StepShape_ContextDependentShapeRepresentation))) {
const auto contextDependentShape =
Handle(StepShape_ContextDependentShapeRepresentation)::DownCast(pdsRef);
auto r1 = contextDependentShape->RepresentationRelation();
auto r2 = contextDependentShape->RepresentedProductRelation();
if (!r1.IsNull() && r1->
IsKind(STANDARD_TYPE(StepRepr_RepresentationRelationshipWithTransformation))) {
auto relWithTrans = Handle(StepRepr_RepresentationRelationshipWithTransformation)::DownCast(r1);
return GetTransformFromShapeRelWithTrans(relWithTrans);
}
}
}
}
}
return transformation;
}
// Helper: given a Product, find its associated ProductDefinition (if any)
static Handle(StepBasic_ProductDefinition) FindProductDefinition(
const Handle(StepBasic_Product) &product,
const Handle(Interface_InterfaceModel) &model, const Interface_Graph &theGraph) {
// A Product typically references a ProductDefinitionFormation
// which references a ProductDefinition. Or in some schemas,
// the Product's "FrameOfReference()" leads to the PDF.
// So we walk the entire model to match the formation to the definition.
//
// If your schema doesn't use Formation(), adapt accordingly (e.g. FrameOfReference).
Handle(StepBasic_ProductDefinitionFormation) formation; {
const Interface_EntityIterator childIter = theGraph.Sharings(product);
for (childIter.Start(); childIter.More(); childIter.Next()) {
Handle(Standard_Transient) ent = childIter.Value();
if (ent->IsKind(STANDARD_TYPE(StepBasic_ProductDefinitionFormation))) {
formation = Handle(StepBasic_ProductDefinitionFormation)::DownCast(ent);
break;
}
}
}
if (formation.IsNull()) {
return nullptr;
} {
const Interface_EntityIterator childIter = theGraph.Sharings(formation);
for (childIter.Start(); childIter.More(); childIter.Next()) {
Handle(Standard_Transient) ent = childIter.Value();
if (ent->IsKind(STANDARD_TYPE(StepBasic_ProductDefinition))) {
return Handle(StepBasic_ProductDefinition)::DownCast(ent);
}
}
}
// The ProductDefinition we want references this formation
for (Standard_Integer i = 1; i <= model->NbEntities(); i++) {
Handle(Standard_Transient) ent = model->Value(i);
if (ent->IsKind(STANDARD_TYPE(StepBasic_ProductDefinition))) {
auto pd = Handle(StepBasic_ProductDefinition)::DownCast(ent);
if (!pd.IsNull() && pd->Formation() == formation) {
return pd;
}
}
}
return nullptr;
}
// Build a map from StepBasic_Product -> entity index, for quick lookup
static std::unordered_map<Handle(StepBasic_Product), int>
BuildProductIndexMap(const Handle(Interface_InterfaceModel) &model) {
std::unordered_map<Handle(StepBasic_Product), int> productToIndex;
for (Standard_Integer i = 1; i <= model->NbEntities(); i++) {
Handle(Standard_Transient) entity = model->Value(i);
if (entity->IsKind(STANDARD_TYPE(StepBasic_Product))) {
auto product = Handle(StepBasic_Product)::DownCast(entity);
if (!product.IsNull()) {
productToIndex[product] = i; // store 1-based index
}
}
}
return productToIndex;
}
// Build a map: parentIndex -> list of child indices
static std::unordered_map<int, std::vector<int> >
BuildAssemblyLinks(const Handle(Interface_InterfaceModel) &model, const Interface_Graph &theGraph) {
// We'll track: Parent's entity index -> [Child's entity index...]
std::unordered_map<int, std::vector<int> > parentToChildren;
// Build a quick map from ProductDefinition -> (Product, index)
// so that given a PD, we know which Product it belongs to
std::unordered_map<Handle(StepBasic_ProductDefinition), int> pdToProductIndex;
// 1) find all products, map each to its product definition
auto productIndexMap = BuildProductIndexMap(model);
for (auto &kv: productIndexMap) {
Handle(StepBasic_Product) product = kv.first;
int productIdx = kv.second;
Handle(StepBasic_ProductDefinition) pd = FindProductDefinition(product, model, theGraph);
if (!pd.IsNull()) {
pdToProductIndex[pd] = productIdx;
}
}
// 2) Traverse NextAssemblyUsageOccurrence to see parent-child relationships
// Each NAUO references two ProductDefinitions: "RelatingProductDefinition"
// (the parent) and "RelatedProductDefinition" (the child).
for (Standard_Integer i = 1; i <= model->NbEntities(); i++) {
Handle(Standard_Transient) ent = model->Value(i);
if (ent->IsKind(STANDARD_TYPE(StepRepr_NextAssemblyUsageOccurrence))) {
auto nauo = Handle(StepRepr_NextAssemblyUsageOccurrence)::DownCast(ent);
if (!nauo.IsNull()) {
Handle(StepBasic_ProductDefinition) pdParent = nauo->RelatingProductDefinition();
Handle(StepBasic_ProductDefinition) pdChild = nauo->RelatedProductDefinition();
auto itParent = pdToProductIndex.find(pdParent);
auto itChild = pdToProductIndex.find(pdChild);
if (itParent != pdToProductIndex.end() && itChild != pdToProductIndex.end()) {
int parentIdx = itParent->second;
int childIdx = itChild->second;
parentToChildren[parentIdx].push_back(childIdx);
}
}
}
}
return parentToChildren;
}
// Build a map: parentIndex -> list of child indices with transformations
static std::unordered_map<int, std::vector<ParentChildRelationship> >
BuildAssemblyLinksWithTransformation(const Handle(Interface_InterfaceModel) &model, const Interface_Graph &theGraph) {
// Map to store parent-child relationships along with transformations
std::unordered_map<int, std::vector<ParentChildRelationship> > parentToChildrenWithTrans;
// Build a quick map from ProductDefinition -> (Product, index)
std::unordered_map<Handle(StepBasic_ProductDefinition), int> pdToProductIndex;
// 1) Find all products, map each to its product definition
auto productIndexMap = BuildProductIndexMap(model);
for (auto &kv: productIndexMap) {
Handle(StepBasic_Product) product = kv.first;
int productIdx = kv.second;
Handle(StepBasic_ProductDefinition) pd = FindProductDefinition(product, model, theGraph);
if (!pd.IsNull()) {
pdToProductIndex[pd] = productIdx;
}
}
// 2) Traverse NextAssemblyUsageOccurrence to see parent-child relationships
// Each NAUO references two ProductDefinitions: "RelatingProductDefinition"
// (the parent) and "RelatedProductDefinition" (the child).
for (Standard_Integer i = 1; i <= model->NbEntities(); i++) {
Handle(Standard_Transient) ent = model->Value(i);
if (ent->IsKind(STANDARD_TYPE(StepRepr_NextAssemblyUsageOccurrence))) {
auto nauo = Handle(StepRepr_NextAssemblyUsageOccurrence)::DownCast(ent);
if (!nauo.IsNull()) {
Handle(StepBasic_ProductDefinition) pdParent = nauo->RelatingProductDefinition();
Handle(StepBasic_ProductDefinition) pdChild = nauo->RelatedProductDefinition();
auto itParent = pdToProductIndex.find(pdParent);
auto itChild = pdToProductIndex.find(pdChild);
if (itParent != pdToProductIndex.end() && itChild != pdToProductIndex.end()) {
int parentIdx = itParent->second;
int childIdx = itChild->second;
// Retrieve the transformation (if available) for the parent-child relationship
gp_Trsf transformation = GetAssemblyInstanceTransformation(nauo, theGraph);
// Add the parent-child relationship with transformation
parentToChildrenWithTrans[parentIdx].emplace_back(parentIdx, childIdx, transformation, i);
}
}
}
}
return parentToChildrenWithTrans;
}
// Definition of the static counter
int ProductNode::instanceCounter = 0;
// Main function: extracts top-level ProductNode trees with transformations
std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph) {
// 1) Build the map of parent->children relationships
// to be replaced by this
const auto parentToChildrenWTransforms = BuildAssemblyLinksWithTransformation(model, theGraph);
// 2) We want to find "root" products (those that never appear as a child)
std::unordered_set<int> allChildren;
for (auto &kv: parentToChildrenWTransforms) {
for (const auto &childRel: kv.second) // Access the ParentChildRelationship (includes transformation)
{
allChildren.insert(childRel.childIndex);
}
}
// 3) Gather all product indices
std::vector<int> allProducts;
for (auto productIndexMap = BuildProductIndexMap(model); auto &val: productIndexMap | std::views::values) {
allProducts.push_back(val);
}
// 4) For each product, if its NOT in allChildren => its a root
std::vector<std::unique_ptr<ProductNode> > roots;
for (int idx: allProducts) {
if (!allChildren.contains(idx)) {
// This is a root product, start with the identity transformation
roots.push_back(
BuildProductNodeWithTransform(idx, parentToChildrenWTransforms, model, theGraph, gp_Trsf()));
}
}
return roots;
}
std::vector<Handle(StepRepr_NextAssemblyUsageOccurrence) > Get_NextAssemblyUsageOccurrences(
const Handle(StepBasic_Product) &product,
const Interface_Graph &theGraph) {
std::vector<Handle(StepRepr_NextAssemblyUsageOccurrence) > nauos;
Interface_EntityIterator childIter = theGraph.Sharings(product);
for (childIter.Start(); childIter.More(); childIter.Next()) {
if (childIter.Value()->IsKind(STANDARD_TYPE(StepBasic_ProductDefinitionFormation))) {
auto pdf = Handle(StepBasic_ProductDefinitionFormation)::DownCast(childIter.Value());
Interface_EntityIterator pdfIter = theGraph.Sharings(pdf);
for (pdfIter.Start(); pdfIter.More(); pdfIter.Next()) {
if (pdfIter.Value()->IsKind(STANDARD_TYPE(StepBasic_ProductDefinition))) {
auto pd = Handle(StepBasic_ProductDefinition)::DownCast(pdfIter.Value());
Interface_EntityIterator pdIter = theGraph.Sharings(pd);
for (pdIter.Start(); pdIter.More(); pdIter.Next()) {
if (pdIter.Value()->IsKind(STANDARD_TYPE(StepRepr_NextAssemblyUsageOccurrence))) {
nauos.push_back(Handle(StepRepr_NextAssemblyUsageOccurrence)::DownCast(pdIter.Value()));
}
}
}
}
}
}
return nauos;
}
Handle(StepRepr_NextAssemblyUsageOccurrence) Get_NextAssemblyUsageOccurrence(const Handle(StepBasic_Product) &product,
const Interface_Graph &theGraph) {
auto result = Get_NextAssemblyUsageOccurrences(product, theGraph);
if (result.size() == 1) {
return result[0];
}
// return last element
return result[result.size() - 1];
}
void add_geometries_to_nodes(const std::vector<std::unique_ptr<ProductNode> > &nodes, const Interface_Graph &theGraph) {
for (auto &ref_node: nodes) {
// 'node' is a std::unique_ptr<ProductNode>&
if (!ref_node) {
// If it's a null pointer, skip it
continue;
}
auto &node = *ref_node;
auto &product = theGraph.Entity(node.entityIndex);
Interface_EntityIterator breps =
Get_Associated_SolidModel_BiDirectional(product,
STANDARD_TYPE(StepShape_SolidModel),
theGraph);
// get the geometry indices
while (breps.More()) {
const auto &entity = breps.Value();
auto entityIndex = theGraph.Model()->Number(entity);
node.geometryInstances.push_back(GeometryInstance(entityIndex));
breps.Next();
}
if (!node.children.empty()) {
add_geometries_to_nodes(node.children, theGraph);
}
}
}
// Recursive function that builds a ProductNode tree with transformations
static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
int productIndex,
const std::unordered_map<int, std::vector<ParentChildRelationship> > &parentToChildrenWTransforms,
const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph,
const gp_Trsf &parentTransform,
ProductNode *parent) {
auto node = std::make_unique<ProductNode>();
node->entityIndex = productIndex;
node->parent = parent;
const Handle(Standard_Transient) ent = model->Value(productIndex);
const auto product = Handle(StepBasic_Product)::DownCast(ent);
if (!product.IsNull() && !product->Name().IsNull()) {
node->name = product->Name()->ToCString();
} else {
node->name = "(unnamed product)";
}
// Combine parent transformation with local transformation
const gp_Trsf absoluteTransform = parentTransform;
node->transformation = absoluteTransform;
// Recurse for children, now using ParentChildRelationship
auto it = parentToChildrenWTransforms.find(productIndex);
if (it != parentToChildrenWTransforms.end()) {
for (const auto &childRel: it->second) // Access the ParentChildRelationship (includes transformation)
{
// Combine parent transformation with the transformation from ParentChildRelationship
gp_Trsf childAbsoluteTransform = absoluteTransform;
childAbsoluteTransform.Multiply(childRel.transformation);
// Build the child node
node->children.push_back(
BuildProductNodeWithTransform(childRel.childIndex, parentToChildrenWTransforms, model, theGraph,
childAbsoluteTransform, node.get()));
}
}
return node;
}
// Iterative function
std::unique_ptr<ProductNode> BuildProductNodeWithTransformIterative(
int rootIndex,
const std::unordered_map<int, std::vector<int> > &parentToChildren,
const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph,
const gp_Trsf &rootTransform) {
// Create the root node
auto rootNode = std::make_unique<ProductNode>();
rootNode->parent = nullptr;
rootNode->entityIndex = rootIndex;
// We'll fill in transform/name/etc. below
// A stack item for our DFS
struct StackItem {
ProductNode *node; // pointer to the node in which we'll store data
int productIndex; // which product index this node corresponds to
gp_Trsf parentTrsf; // the parent's final transform to be combined with local transform
};
std::stack<StackItem> stack;
stack.push({rootNode.get(), rootIndex, rootTransform});
// A set to track visited product indices
// to avoid infinite loops if there's a cycle
std::unordered_set<int> visited;
visited.insert(rootIndex);
while (!stack.empty()) {
auto [nodePtr, productIdx, accumulatedTrsf] = stack.top();
stack.pop();
// --- 1) Safety checks to avoid segfaults ---
// If productIdx is invalid (e.g. out of range for the model),
// we skip filling data and proceed. Depending on your data,
// you might want to throw an exception instead.
if (productIdx < 1 || productIdx > model->NbEntities()) {
// Mark something or skip
nodePtr->name = "(invalid index)";
// nodePtr->instanceIndex = -1;
continue;
}
// --- 2) Look up the product from the model ---
Handle(Standard_Transient) ent = model->Value(productIdx);
if (ent.IsNull()) {
nodePtr->name = "(model->Value() returned null)";
// nodePtr->instanceIndex = -1;
continue;
}
auto product = Handle(StepBasic_Product)::DownCast(ent);
if (product.IsNull()) {
nodePtr->name = "(not a StepBasic_Product)";
// nodePtr->instanceIndex = -1;
continue;
}
// --- 3) Compute local transform and combine with parent's ---
auto nauo = Get_NextAssemblyUsageOccurrence(product, theGraph);
if (!nauo.IsNull()) {
gp_Trsf localTransform = GetAssemblyInstanceTransformation(nauo, theGraph);
gp_Trsf finalTransform = accumulatedTrsf;
finalTransform.Multiply(localTransform);
nodePtr->transformation = finalTransform;
// Instance index from the graph
nodePtr->instanceIndex = theGraph.Model()->Number(nauo);
} else {
// If NAUO is invalid for some reason
nodePtr->transformation = accumulatedTrsf;
// nodePtr->instanceIndex = -1;
}
// --- 4) Fill the node's name ---
if (!product->Name().IsNull()) {
nodePtr->name = product->Name()->ToCString();
} else {
nodePtr->name = "(unnamed product)";
}
// --- 5) Create child nodes and push them onto the stack ---
auto it = parentToChildren.find(productIdx);
if (it != parentToChildren.end()) {
for (int childIdx: it->second) {
// If we've already seen this index, skip to avoid cycles
if (!visited.insert(childIdx).second) {
continue;
}
// Create a child node by value in the parent's children vector
nodePtr->children.emplace_back();
std::unique_ptr<ProductNode> &childRef = nodePtr->children.back();
childRef->parent = nodePtr;
childRef->entityIndex = childIdx;
// We'll fill all the child details once we pop from the stack
// For now, just push onto the stack
stack.push({childRef.get(), childIdx, nodePtr->transformation});
}
}
}
return rootNode;
}
// Helper function to serialize a gp_Trsf (4x4 transformation matrix) to JSON
static void TransformationToJson(const gp_Trsf &transform, std::ostream &os, int indentLevel) {
// Utility lambda to insert some indentation spaces
auto indent = [&](int level) {
for (int i = 0; i < level; i++) os << " ";
};
indent(indentLevel);
os << "\"transformation\": [\n";
// gp_Trsf stores a 3x4 matrix (rotation and translation), so we manually append rows
for (int i = 1; i <= 3; i++) {
indent(indentLevel + 1);
os << "[";
for (int j = 1; j <= 4; j++) // OpenCASCADE uses 1-based indexing
{
os << transform.Value(i, j);
if (j < 4) os << ", ";
}
os << "]";
if (i < 3) os << ",\n";
}
// Add the homogeneous row for a 4x4 matrix
os << ",\n";
indent(indentLevel + 1);
os << "[0, 0, 0, 1]\n";
indent(indentLevel);
os << "]";
}
// Simple recursive JSON builder
static void ProductNodeToJson(const ProductNode &node, std::ostream &os, int indentLevel) {
// Utility lambda to insert some indentation spaces
auto indent = [&](int level) {
for (int i = 0; i < level; i++) os << " ";
};
indent(indentLevel);
os << "{\n";
indent(indentLevel + 1);
os << "\"entityIndex\": " << node.entityIndex << ",\n";
indent(indentLevel + 1);
os << "\"InstanceIndex\": " << node.instanceIndex << ",\n";
indent(indentLevel + 1);
os << "\"targetIndex\": " << node.targetIndex.Tag() << ",\n";
indent(indentLevel + 1);
os << "\"name\": \"" << node.name << "\",\n";
indent(indentLevel + 1);
os << "\"geometryIndices\": [";
for (size_t i = 0; i < node.geometryInstances.size(); i++) {
os << node.geometryInstances[i].entityIndex;
if (i + 1 < node.geometryInstances.size()) {
os << ", ";
}
}
os << "],\n";
// Add the transformation matrix to the JSON output
TransformationToJson(node.transformation, os, indentLevel + 1);
os << ",\n";
indent(indentLevel + 1);
os << "\"children\": [\n";
for (size_t i = 0; i < node.children.size(); i++) {
ProductNodeToJson(*node.children[i], os, indentLevel + 2);
if (i + 1 < node.children.size()) {
os << ",";
}
os << "\n";
}
indent(indentLevel + 1);
os << "]\n";
indent(indentLevel);
os << "}";
}
std::string ExportHierarchyToJson(const std::vector<std::unique_ptr<ProductNode> > &roots) {
std::ostringstream oss;
oss << "[\n";
for (size_t i = 0; i < roots.size(); i++) {
ProductNodeToJson(*roots[i], oss, 1);
if (i + 1 < roots.size()) {
oss << ",";
}
oss << "\n";
}
oss << "]\n";
return oss.str();
}

View File

@ -0,0 +1,98 @@
//
// Created by ofskrand on 09.01.2025.
//
#ifndef STEP_TREE_H
#define STEP_TREE_H
#include <gp_Trsf.hxx>
#include <Interface_InterfaceModel.hxx>
#include <Interface_Graph.hxx>
#include <Standard_Handle.hxx> // For Handle
#include <StepRepr_NextAssemblyUsageOccurrence.hxx>
#include <coroutine>
#include <string>
#include <TDF_Label.hxx>
#include <vector>
#include <unordered_map>
struct ProcessResult {
mutable bool added_to_model;
mutable std::string skip_reason;
mutable int geometryIndex;
};
struct GeometryInstance {
int entityIndex{};
gp_Trsf transformation;
};
// Updated struct
struct ProductNode {
// Static counter for generating unique instance indices
static int instanceCounter;
ProductNode* parent; // new field to keep track of the node's parent
int entityIndex;
std::string name;
std::vector<std::unique_ptr<ProductNode> > children;
int instanceIndex;
std::vector<GeometryInstance> geometryInstances;
TDF_Label targetIndex;
gp_Trsf transformation;
ProcessResult processResult = ProcessResult(false, "");
// Constructor to initialize and assign unique instanceIndex
ProductNode() : instanceIndex(instanceCounter++) {}
void collectNodesWithGeometry(std::vector<const ProductNode *> &result) const {
if (!geometryInstances.empty()) {
result.push_back(this);
}
for (const auto &child: children) {
child->collectNodesWithGeometry(result);
}
}
};
// Define a struct to hold the parent-child relationship and transformation
struct ParentChildRelationship {
int parentIndex;
int childIndex;
gp_Trsf transformation; // Transformation between parent and child
int nauoIndex;
ParentChildRelationship(int pIdx, int cIdx, const gp_Trsf& transform, int nauoIdx)
: parentIndex(pIdx), childIndex(cIdx), transformation(transform), nauoIndex(nauoIdx) {
}
};
std::vector<std::unique_ptr<ProductNode> > ExtractProductHierarchy(const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph);
std::string ExportHierarchyToJson(const std::vector<std::unique_ptr<ProductNode> > &roots);
void add_geometries_to_nodes(const std::vector<std::unique_ptr<ProductNode> > &nodes, const Interface_Graph &theGraph);
gp_Trsf GetTransformationMatrix(
const Handle(StepRepr_NextAssemblyUsageOccurrence) &nauo,
const Interface_Graph &theGraph);
static std::unique_ptr<ProductNode> BuildProductNodeWithTransform(
int productIndex,
const std::unordered_map<int, std::vector<ParentChildRelationship>>& parentToChildrenWTransforms,
const Handle(Interface_InterfaceModel)& model,
const Interface_Graph& theGraph,
const gp_Trsf& parentTransform = gp_Trsf(),
ProductNode* parent = nullptr);
static std::unique_ptr<ProductNode> BuildProductNodeWithTransformIterative(
int rootIndex,
const std::unordered_map<int, std::vector<int> > &parentToChildren,
const Handle(Interface_InterfaceModel) &model,
const Interface_Graph &theGraph,
const gp_Trsf &rootTransform = gp_Trsf());
#endif //STEP_TREE_H

View File

@ -0,0 +1,165 @@
#include "step_writer.h"
#include <BRepBuilderAPI_Transform.hxx>
#include <iostream>
#include <filesystem>
#include <BRep_Builder.hxx>
#include <Interface_Static.hxx>
#include <Quantity_Color.hxx>
#include <Quantity_TypeOfColor.hxx>
#include <RWGltf_CafWriter.hxx>
#include <STEPCAFControl_Writer.hxx>
#include <TCollection_ExtendedString.hxx>
#include <TDataStd_Name.hxx>
#include <TDocStd_Application.hxx>
#include <TDocStd_Document.hxx>
#include <TopoDS_Compound.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XSControl_WorkSession.hxx>
#include <TDF_Label.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include "helpers.h"
#include "step_tree.h"
#include "../../geom/Color.h"
#include <Standard_Handle.hxx> // For Handle
// Constructor with only top-level name
StepStore::StepStore(const std::string &top_level_name) {
initialize();
tll_ = shape_tool_->AddShape(comp_, Standard_True);
set_name(tll_, top_level_name);
}
// Constructor with product hierarchy
StepStore::StepStore(const std::vector<std::unique_ptr<ProductNode> > &product_hierarchy) {
initialize();
create_hierarchy(product_hierarchy, tll_);
}
// Private initialization function
void StepStore::initialize() {
app_ = new TDocStd_Application();
doc_ = new TDocStd_Document(TCollection_ExtendedString("XmlOcaf"));
app_->InitDocument(doc_);
shape_tool_ = XCAFDoc_DocumentTool::ShapeTool(doc_->Main());
XCAFDoc_ShapeTool::SetAutoNaming(false);
color_tool_ = XCAFDoc_DocumentTool::ColorTool(doc_->Main());
comp_builder_.MakeCompound(comp_);
}
// Create a hierarchy of products
void StepStore::create_hierarchy(const std::vector<std::unique_ptr<ProductNode> > &nodes,
const TDF_Label &parent_label) {
for (auto &node: nodes) {
TDF_Label child_label = shape_tool_->NewShape();
TDF_Label target_label;
if (parent_label.IsNull()) {
tll_ = shape_tool_->AddShape(comp_, Standard_True);
target_label = tll_;
} else {
target_label = shape_tool_->AddComponent(parent_label, child_label, TopLoc_Location());
}
set_name(child_label, node->name);
product_labels_[node->name] = child_label;
entity_labels_[node->instanceIndex] = child_label;
node->targetIndex = target_label;
if (!node->children.empty()) {
create_hierarchy(node->children, child_label);
}
}
}
// Set name for a label
void StepStore::set_name(const TDF_Label &label, const std::string &name) {
TDataStd_Name::Set(label, TCollection_ExtendedString(name.c_str()));
}
// Set color for a label
void StepStore::set_color(const TDF_Label &label, const Color &rgb_color,
const Handle(XCAFDoc_ColorTool) &color_tool) {
Quantity_Color color(rgb_color.r, rgb_color.g, rgb_color.b, Quantity_TOC_RGB);
color_tool->SetColor(label, color, XCAFDoc_ColorGen);
}
// Add a shape
void StepStore::add_shape(const TopoDS_Shape &shape, const std::string &name,
const Color &rgb_color, const ProductNode &dummy_product) {
const TDF_Label dummy_label = entity_labels_[dummy_product.instanceIndex];
const TDF_Label parent_label = entity_labels_[dummy_product.parent->instanceIndex];
if (dummy_label.IsNull()) {
throw std::runtime_error("Parent product not found: " + dummy_product.name);
}
const auto location = TopLoc_Location(dummy_product.transformation);
// replace the dummy label with the actual shape
// get parent of dummy label
shape_tool_->RemoveComponent(dummy_label);
const TDF_Label shape_label = shape_tool_->AddShape(shape, Standard_False, Standard_False);
shape_tool_->AddComponent(parent_label, shape_label, location);
set_color(shape_label, rgb_color, color_tool_);
set_name(shape_label, name);
const auto new_shape = XCAFDoc_ShapeTool::GetShape(shape_label);
// Add any additional location transformations from product to shape
BRepBuilderAPI_Transform shapeTransform(dummy_product.transformation);
shapeTransform.Perform(new_shape, Standard_False);
}
// Export the STEP file
void StepStore::to_step(const std::filesystem::path &step_file) const {
shape_tool_->UpdateAssemblies();
if (!step_file.parent_path().empty() && step_file.parent_path() != "") {
create_directories(step_file.parent_path());
}
const Handle(XSControl_WorkSession) session = new XSControl_WorkSession();
STEPCAFControl_Writer writer(session, Standard_False);
writer.SetColorMode(Standard_True);
writer.SetNameMode(Standard_True);
Interface_Static::SetCVal("write.step.assembly", "ON");
Interface_Static::SetCVal("write.step.product.context", "PRODUCT");
Interface_Static::SetCVal("write.step.unit", "m");
Interface_Static::SetCVal("write.step.schema", "AP242");
writer.Transfer(doc_, STEPControl_AsIs);
const IFSelect_ReturnStatus status = writer.Write(step_file.string().c_str());
if (status != IFSelect_RetDone) {
throw std::runtime_error("STEP export failed");
}
std::cout << "STEP export status: " << status << "\n";
}
void StepStore::to_glb(const std::filesystem::path &glb_file) const {
shape_tool_->UpdateAssemblies();
RWGltf_CafWriter writer(glb_file.c_str(), true); // true for binary format
// Additional file information (can be empty if not needed)
const TColStd_IndexedDataMapOfStringString file_info;
// Progress indicator (can be null if progress tracking is not needed)
const Message_ProgressRange progress;
// if output parent directory is != "" and does not exist, create it
if (const std::filesystem::path glb_dir = glb_file.parent_path(); !glb_dir.empty() && !exists(glb_dir)) {
create_directories(glb_dir);
}
if (!writer.Perform(doc_, file_info, progress)) {
throw std::runtime_error("Error writing GLB file");
}
}

View File

@ -0,0 +1,53 @@
// AdaCPPStepWriter.h
#pragma once
#include <BRep_Builder.hxx>
#include <filesystem>
#include <TDocStd_Application.hxx>
#include <TDocStd_Document.hxx>
#include <TopoDS_Compound.hxx>
#include <TDF_Label.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include "step_tree.h"
#include "../../geom/Color.h"
class StepStore {
public:
Handle(TDocStd_Document) doc_;
explicit StepStore(const std::string& top_level_name = "Assembly");
explicit StepStore(const std::vector<std::unique_ptr<ProductNode>>& product_hierarchy);
void add_shape(const TopoDS_Shape& shape, const std::string& name, const Color& rgb_color,
const ProductNode& parent_node);
void to_step(const std::filesystem::path& step_file) const;
void to_glb(const std::filesystem::path& glb_file) const;
private:
// Handles (smart pointers) to OCC classes
Handle(TDocStd_Application) app_;
Handle(XCAFDoc_ShapeTool) shape_tool_;
Handle(XCAFDoc_ColorTool) color_tool_;
TopoDS_Compound comp_;
BRep_Builder comp_builder_;
TDF_Label tll_;
// Map to store product name to TDF_Label mapping for hierarchy
std::unordered_map<std::string, TDF_Label> product_labels_;
// Map to store source entity index to TDF_Label mapping for shapes
std::unordered_map<int, TDF_Label> entity_labels_;
void initialize();
void create_hierarchy(const std::vector<std::unique_ptr<ProductNode>> &nodes, const TDF_Label &parent_label);
static void set_name(const TDF_Label& label, const std::string& name);
static void set_color(const TDF_Label& label, const Color& rgb_color, const Handle(XCAFDoc_ColorTool)& color_tool);
};

View File

@ -0,0 +1,39 @@
//
// Created by ofskrand on 12.09.2024.
//
#include <windows.h>
#include "sc_parser.h"
#include <filesystem>
#include <iostream>
#include "cllazyfile/lazyInstMgr.h"
#include <psapi.h>
#include "../../config_structs.h"
// Function to print memory usage
void printMemoryUsage()
{
PROCESS_MEMORY_COUNTERS_EX pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
{
std::cout << "Memory usage: " << pmc.WorkingSetSize / 1024 << " KB\n";
}
}
// Function to parse a STEP file using lazy loading
void lazy_step_parser(const GlobalConfig& config)
{
auto mgr = std::make_unique<lazyInstMgr>();
printMemoryUsage(); // Memory before opening the file
mgr->openFile(config.stpFile.string());
printMemoryUsage(); // Memory after opening the file
int instances = mgr->totalInstanceCount();
std::cout << "Total instances: " << instances << "\n";
printMemoryUsage(); // Memory after counting instances
}

View File

@ -0,0 +1,11 @@
//
// Created by ofskrand on 12.09.2024.
//
#ifndef SC_PARSER_H
#define SC_PARSER_H
#include "../../config_structs.h"
void lazy_step_parser(const GlobalConfig& config);
#endif //SC_PARSER_H

175
src/cadit/tinygltf/tiny.cpp Normal file
View File

@ -0,0 +1,175 @@
//
// Created by Kristoffer on 26/07/2023.
//
#include <iostream>
#include "tinyload.h"
#include "../../geom/Mesh.h"
#include "../../binding_core.h"
#include "../../visit/tess_helpers.h"
#include "../../helpers/helpers.h"
std::pair<std::vector<double>, std::vector<double>> calculateBounds(const std::vector<float>& positions) {
assert(positions.size() % 3 == 0); // Ensure there is 3D data
std::vector<double> minValues = { std::numeric_limits<double>::max(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max() };
std::vector<double> maxValues = { std::numeric_limits<double>::lowest(),
std::numeric_limits<double>::lowest(),
std::numeric_limits<double>::lowest() };
for (size_t i = 0; i < positions.size(); i += 3) {
minValues[0] = std::min(static_cast<double>(minValues[0]), static_cast<double>(positions[i]));
minValues[1] = std::min(static_cast<double>(minValues[1]), static_cast<double>(positions[i + 1]));
minValues[2] = std::min(static_cast<double>(minValues[2]), static_cast<double>(positions[i + 2]));
maxValues[0] = std::max(static_cast<double>(maxValues[0]), static_cast<double>(positions[i]));
maxValues[1] = std::max(static_cast<double>(maxValues[1]), static_cast<double>(positions[i + 1]));
maxValues[2] = std::max(static_cast<double>(maxValues[2]), static_cast<double>(positions[i + 2]));
}
return {minValues, maxValues};
}
void AddMesh(tinygltf::Model &model, const std::string &name, Mesh my_mesh) {
std::vector<float> positions = my_mesh.positions;
std::vector<uint32_t> indices = my_mesh.indices;
// Append buffer for position data
tinygltf::Buffer posBuffer;
posBuffer.data = std::vector<unsigned char>(reinterpret_cast<const unsigned char*>(positions.data()),
reinterpret_cast<const unsigned char*>(positions.data() + positions.size()));
model.buffers.push_back(posBuffer);
// Buffer for indices
tinygltf::Buffer indexBuffer;
indexBuffer.data = std::vector<unsigned char>(reinterpret_cast<const unsigned char*>(indices.data()),
reinterpret_cast<const unsigned char*>(indices.data() + indices.size()));
model.buffers.push_back(indexBuffer);
// BufferView for positions
tinygltf::BufferView posBufferView;
posBufferView.buffer = model.buffers.size() - 2;
posBufferView.byteLength = posBuffer.data.size();
posBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; // Add this line
model.bufferViews.push_back(posBufferView);
// BufferView for indices
tinygltf::BufferView indexBufferView;
indexBufferView.buffer = model.buffers.size() - 1;
indexBufferView.byteLength = indexBuffer.data.size();
indexBufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; // Add this line
model.bufferViews.push_back(indexBufferView);
// Accessor for positions
tinygltf::Accessor posAccessor;
posAccessor.bufferView = model.bufferViews.size() - 2;
posAccessor.byteOffset = 0;
posAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
posAccessor.count = positions.size() / 3;
posAccessor.type = TINYGLTF_TYPE_VEC3;
// Calculate and add min and max here
auto [minValues, maxValues] = calculateBounds(positions);
posAccessor.minValues = minValues;
posAccessor.maxValues = maxValues;
model.accessors.push_back(posAccessor);
// Accessor for indices
tinygltf::Accessor indexAccessor;
indexAccessor.bufferView = model.bufferViews.size() - 1;
indexAccessor.byteOffset = 0;
indexAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; // uint32_t indices
indexAccessor.count = indices.size();
indexAccessor.type = TINYGLTF_TYPE_SCALAR;
model.accessors.push_back(indexAccessor);
// Create a mesh that references the accessors
tinygltf::Mesh mesh;
tinygltf::Primitive primitive;
primitive.attributes["POSITION"] = model.accessors.size() - 2;
primitive.indices = model.accessors.size() - 1;
primitive.mode = TINYGLTF_MODE_TRIANGLES;
mesh.primitives.push_back(primitive);
// Create a material with PBR properties
tinygltf::Material material;
material.pbrMetallicRoughness.baseColorFactor = {my_mesh.color.r, my_mesh.color.g, my_mesh.color.b, my_mesh.color.a};
material.pbrMetallicRoughness.metallicFactor = 1.0;
material.pbrMetallicRoughness.roughnessFactor = 0.5;
material.alphaMode = "OPAQUE";
// Add the material to the model
int materialIndex = model.materials.size();
model.materials.push_back(material);
// Assign the material to the primitive
mesh.primitives[0].material = materialIndex; // Modify this line
model.meshes.push_back(mesh);
// Create a node that references the mesh
tinygltf::Node node;
node.mesh = model.meshes.size() - 1;
model.nodes.push_back(node);
// Add the node to the scene
model.scenes[0].nodes.push_back(model.nodes.size() - 1);
}
int write_to_gltf(const std::string& filename, Mesh mesh) {
tinygltf::Model model;
// Create a scene
tinygltf::Scene scene;
model.scenes.push_back(scene);
model.defaultScene = 0;
AddMesh(model, "mesh", mesh);
// Save to file
tinygltf::TinyGLTF gltf;
if (!gltf.WriteGltfSceneToFile(&model, filename, true, true, true, false)) {
std::cerr << "Failed to write glTF file" << std::endl;
return -1;
}
return 0;
}
// take in a list of box dimensions and origins and write to step file using the AdaCPPStepWriter class
int write_boxes_to_gltf(const std::string &filename, const std::vector<std::vector<float>> &box_origins,
const std::vector<std::vector<float>> &box_dims) {
tinygltf::Model model;
// Create a scene
tinygltf::Scene scene;
model.scenes.push_back(scene);
model.defaultScene = 0;
for (int i = 0; i < box_origins.size(); i++) {
TopoDS_Solid box = create_box(box_origins[i], box_dims[i]);
Mesh mesh = tessellate_shape(0, box, true, 1.0, false);
mesh.color = random_color();
AddMesh(model, "mesh", mesh);
}
// If filename contains .glb set variable "glb" to true
bool glb = filename.find(".glb") != std::string::npos;
// Save to file
tinygltf::TinyGLTF gltf;
if (!gltf.WriteGltfSceneToFile(&model, filename, true, true, true, glb)) {
std::cerr << "Failed to write glTF file" << std::endl;
return -1;
}
return 0;
}
void tiny_gltf_module(nb::module_ &m) {
m.def("write_mesh_to_gltf", &write_to_gltf, "filename"_a, "mesh"_a, "Write a Mesh to GLTF");
m.def("write_boxes_to_gltf", &write_boxes_to_gltf, "filename"_a, "box_origins"_a, "box_dims"_a, "Write a list of boxes to GLTF");
}

12
src/cadit/tinygltf/tiny.h Normal file
View File

@ -0,0 +1,12 @@
//
// Created by Kristoffer on 26/07/2023.
//
#ifndef ADA_CPP_TINY_H
#define ADA_CPP_TINY_H
#include "../../binding_core.h"
void tiny_gltf_module(nb::module_&m);
#endif //ADA_CPP_TINY_H

View File

@ -0,0 +1,10 @@
//
// Created by Kristoffer on 23/01/2024.
//
#pragma once
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "tiny_gltf.h"

5
src/geom/Color.cpp Normal file
View File

@ -0,0 +1,5 @@
// Color.cpp
#include "Color.h"
Color::Color(float r, float g, float b, float a)
: r(r), g(g), b(b), a(a) {}

18
src/geom/Color.h Normal file
View File

@ -0,0 +1,18 @@
//
// Created by Kristoffer on 07/05/2023.
//
#ifndef NANO_OCCT_COLOR_H
#define NANO_OCCT_COLOR_H
class Color {
public:
float r;
float g;
float b;
float a;
explicit Color(float r=0.5f, float g=0.5f, float b=0.5f, float a = 1.0f);
};
#endif //NANO_OCCT_COLOR_H

17
src/geom/GroupReference.h Normal file
View File

@ -0,0 +1,17 @@
//
// Created by Kristoffer on 07/05/2023.
//
#ifndef NANO_OCCT_GROUPREFERENCE_H
#define NANO_OCCT_GROUPREFERENCE_H
class GroupReference {
public:
int node_id;
int start;
int length;
GroupReference(int node_id, int start, int length);
};
#endif //NANO_OCCT_GROUPREFERENCE_H

32
src/geom/Mesh.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef NANO_OCCT_MESH_H
#define NANO_OCCT_MESH_H
#include <vector>
#include <cstdint>
#include <optional>
#include "MeshType.h"
#include "GroupReference.h"
#include "Color.h"
class Mesh {
public:
Mesh(int id,
std::vector<float> positions,
std::vector<uint32_t> faces,
std::vector<uint32_t> edges = {},
std::vector<float> normals = {},
MeshType mesh_type = MeshType::TRIANGLES,
Color color = Color(),
std::vector<GroupReference> group_reference = {});
int id;
std::vector<float> positions;
std::vector<uint32_t> indices;
std::vector<uint32_t> edges;
std::vector<float> normals;
MeshType mesh_type;
Color color;
std::vector<GroupReference> group_reference;
};
#endif //NANO_OCCT_MESH_H

20
src/geom/MeshType.h Normal file
View File

@ -0,0 +1,20 @@
//
// Created by Kristoffer on 07/05/2023.
//
#ifndef NANO_OCCT_MESHTYPE_H
#define NANO_OCCT_MESHTYPE_H
enum class MeshType {
POINTS = 0,
LINES = 1,
LINE_LOOP = 2,
LINE_STRIP = 3,
TRIANGLES = 4,
TRIANGLE_STRIP = 5,
TRIANGLE_FAN = 6
};
MeshType from_int(int value);
#endif //NANO_OCCT_MESHTYPE_H

43
src/geom/Models.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <optional>
#include <utility>
#include <vector>
#include <TopoDS_Shape.hxx>
#include <stdexcept>
#include "OccShape.h"
#include "MeshType.h"
#include "Mesh.h"
#include "GroupReference.h"
#include "Color.h"
Color::Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {}
MeshType from_int(int value) {
if (value < 0 || value > 6) {
throw std::out_of_range("Invalid MeshType value");
}
return static_cast<MeshType>(value);
}
Mesh::Mesh(int id, std::vector<float> positions, std::vector<uint32_t> faces,
std::vector<uint32_t> edges, std::vector<float> normals,
MeshType mesh_type, Color color, std::vector<GroupReference> group_reference)
: id(id),
positions(std::move(positions)),
indices(std::move(faces)),
edges(std::move(edges)),
normals(std::move(normals)),
mesh_type(mesh_type),
color(std::move(color)),
group_reference(std::move(group_reference)) {}
OccShape::OccShape(TopoDS_Shape shape,
Color color,
int num_tot_entities,
std::optional<std::string> name)
: shape(std::move(shape)),
color(color),
num_tot_entities(num_tot_entities),
name(std::move(name)) {}
GroupReference::GroupReference(int node_id, int start, int length)
: node_id(node_id), start(start), length(length) {}

27
src/geom/OccShape.h Normal file
View File

@ -0,0 +1,27 @@
//
// Created by Kristoffer on 07/05/2023.
//
#ifndef NANO_OCCT_OCCSHAPE_H
#define NANO_OCCT_OCCSHAPE_H
#include <optional>
#include <vector>
#include <TopoDS_Shape.hxx>
#include "Color.h"
class OccShape {
public:
explicit OccShape(TopoDS_Shape shape,
Color color = Color(),
int num_tot_entities = 0,
std::optional<std::string> name = std::nullopt);
TopoDS_Shape shape;
Color color;
int num_tot_entities;
std::optional<std::string> name;
};
#endif //NANO_OCCT_OCCSHAPE_H

18
src/geom/geometries.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "geometries.h"
//
// Created by Kristoffer on 30/07/2023.
//
int Shape::next_id = 0;
void shape_module(nb::module_ &m) {
nb::class_<Shape>(m, "Shape")
.def_ro("id", &Shape::id);
nb::class_<Box, Shape>(m, "Box")
.def(nb::init<std::vector<double>, double, double, double>())
.def_rw("origin", &Box::origin)
.def_rw("width", &Box::width)
.def_rw("length", &Box::length)
.def_rw("height", &Box::height);
};

34
src/geom/geometries.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef ADA_CPP_GEOMETRIES_H
#define ADA_CPP_GEOMETRIES_H
#include <CGAL/Simple_cartesian.h>
#include "../binding_core.h"
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Vector_3 Vector_3;
struct Shape {
// create a global unique int id for each shape
int id;
// upon instantiation of a shape, increment the global id
static int next_id;
Shape() : id(next_id++) {}
};
struct Box : Shape {
Point_3 origin;
double width;
double length;
double height;
public:
Box(const std::vector<double> &origin, double width, double length, double height) : origin(origin[0], origin[1],
origin[2]),
width(width), length(length),
height(height) {}
};
void shape_module(nb::module_ &m);
#endif //ADA_CPP_GEOMETRIES_H

2
test-log.json Normal file
View File

@ -0,0 +1,2 @@
[
]

BIN
test.glb Normal file

Binary file not shown.

209
test_wrapper.py Normal file
View File

@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""
测试Python包装器功能
"""
import os
import sys
import subprocess
import tempfile
import shutil
def test_help():
"""测试帮助信息"""
print("测试 --help 参数...")
result = subprocess.run([sys.executable, 'main.py', '--help'],
capture_output=True, text=True)
if result.returncode == 0:
print("✓ 帮助信息显示正常")
return True
else:
print("✗ 帮助信息显示失败")
print(result.stderr)
return False
def test_missing_args():
"""测试缺少参数的情况"""
print("测试缺少参数...")
result = subprocess.run([sys.executable, 'main.py'],
capture_output=True, text=True)
if result.returncode != 0:
print("✓ 正确处理缺少参数的情况")
return True
else:
print("✗ 未能正确处理缺少参数")
return False
def test_invalid_input():
"""测试无效输入文件"""
print("测试无效输入文件...")
result = subprocess.run([sys.executable, 'main.py', 'nonexistent.stp', 'output.glb'],
capture_output=True, text=True)
if result.returncode != 0 and "输入文件不存在" in result.stdout:
print("✓ 正确处理无效输入文件")
return True
else:
print("✗ 未能正确处理无效输入文件")
print(result.stdout)
return False
def test_invalid_output():
"""测试无效输出文件扩展名"""
print("测试无效输出文件扩展名...")
# 创建临时输入文件
with tempfile.NamedTemporaryFile(suffix='.stp', delete=False) as tmp_input:
tmp_input.write(b"dummy content")
tmp_input_path = tmp_input.name
try:
result = subprocess.run([sys.executable, 'main.py', tmp_input_path, 'output.txt'],
capture_output=True, text=True)
if result.returncode != 0 and "输出文件必须是.glb格式" in result.stdout:
print("✓ 正确处理无效输出文件扩展名")
return True
else:
print("✗ 未能正确处理无效输出文件扩展名")
print(result.stdout)
return False
finally:
os.unlink(tmp_input_path)
def test_quality_presets():
"""测试质量预设"""
print("测试质量预设...")
# 创建临时输入文件
with tempfile.NamedTemporaryFile(suffix='.stp', delete=False) as tmp_input:
tmp_input.write(b"dummy content")
tmp_input_path = tmp_input.name
# 创建临时输出目录
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_output_path = os.path.join(tmp_dir, 'output.glb')
try:
# 测试不同质量等级
for quality in ['low', 'medium', 'high', 'ultra']:
print(f" 测试质量等级: {quality}")
result = subprocess.run([
sys.executable, 'main.py',
tmp_input_path, tmp_output_path,
'--quality', quality
], capture_output=True, text=True)
# 应该失败(因为没有找到可执行文件),但参数解析应该成功
if "未找到STP2GLB.exe可执行文件" in result.stdout:
print(f"{quality} 质量等级参数解析正常")
else:
print(f"{quality} 质量等级参数解析异常")
print(result.stdout)
return False
return True
finally:
os.unlink(tmp_input_path)
def test_custom_quality():
"""测试自定义质量参数"""
print("测试自定义质量参数...")
# 创建临时输入文件
with tempfile.NamedTemporaryFile(suffix='.stp', delete=False) as tmp_input:
tmp_input.write(b"dummy content")
tmp_input_path = tmp_input.name
# 创建临时输出目录
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_output_path = os.path.join(tmp_dir, 'output.glb')
try:
# 测试custom质量但没有指定参数
result = subprocess.run([
sys.executable, 'main.py',
tmp_input_path, tmp_output_path,
'--quality', 'custom'
], capture_output=True, text=True)
if result.returncode != 0 and "必须指定--linear-deflection或--angular-deflection" in result.stdout:
print(" ✓ 正确处理custom质量缺少参数")
else:
print(" ✗ 未能正确处理custom质量缺少参数")
print(result.stdout)
return False
# 测试custom质量并指定参数
result = subprocess.run([
sys.executable, 'main.py',
tmp_input_path, tmp_output_path,
'--quality', 'custom',
'--linear-deflection', '0.05',
'--angular-deflection', '0.2'
], capture_output=True, text=True)
# 应该失败(因为没有找到可执行文件),但参数解析应该成功
if "未找到STP2GLB.exe可执行文件" in result.stdout:
print(" ✓ custom质量参数解析正常")
return True
else:
print(" ✗ custom质量参数解析异常")
print(result.stdout)
return False
finally:
os.unlink(tmp_input_path)
def main():
"""主测试函数"""
print("Python包装器功能测试")
print("=" * 40)
# 检查main.py是否存在
if not os.path.exists('main.py'):
print("错误: main.py 文件不存在")
return 1
tests = [
test_help,
test_missing_args,
test_invalid_input,
test_invalid_output,
test_quality_presets,
test_custom_quality,
]
passed = 0
total = len(tests)
for test_func in tests:
try:
if test_func():
passed += 1
print()
except Exception as e:
print(f"✗ 测试异常: {e}")
print()
print("=" * 40)
print(f"测试结果: {passed}/{total} 通过")
if passed == total:
print("✓ 所有测试通过!")
return 0
else:
print("✗ 部分测试失败")
return 1
if __name__ == '__main__':
sys.exit(main())

4
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.stp
*.glb
*.gltf
*.msh

57
tests/tests.cmake Normal file
View File

@ -0,0 +1,57 @@
add_test(NAME plate COMMAND STP2GLB
--stp ${CMAKE_CURRENT_SOURCE_DIR}/files/flat_plate_abaqus_10x10_m_wColors.stp
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/flat_plate_abaqus_10x10_m_wColors.glb
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME debug_plate COMMAND STP2GLB
--stp ${CMAKE_CURRENT_SOURCE_DIR}/files/flat_plate_abaqus_10x10_m_wColors.stp
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/flat_plate_abaqus_10x10_m_wColors.glb
--debug
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME as1 COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-std.glb
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME debug_as1 COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-debug.glb
--debug
--solid-only
--max-geometry-num=0
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME debug_as1_mini COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214-mini.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-mini-debug.glb
--debug
--solid-only
--max-geometry-num=0
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
add_test(NAME debug_as1_filter COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/files/as1-oc-214.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/as1-oc-214-filtered.glb
--debug
--solid-only
--filter-names-include="l-bracket"
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/temp/really_large.stp")
add_test(NAME stp_glb_debug_large COMMAND STP2GLB
--stp "${CMAKE_CURRENT_SOURCE_DIR}/temp/really_large.stp"
--glb ${CMAKE_CURRENT_SOURCE_DIR}/temp/really_large-v2.glb
--debug
--solid-only
--max-geometry-num=0
--filter-names-file-exclude=${CMAKE_CURRENT_SOURCE_DIR}/temp/skip-these-nodes.txt
WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/bin"
)
endif ()