Author SHA1 Message Date
NotBigGhostandClaude Opus 5 3b66719029 Исследование геометрических ядер САПР: выбор и обоснование
Сравнение открытых и закрытых (с бесплатными лицензиями) ядер по пяти
требованиям задачи плюс лицензионный риск шестым. Кандидаты проверены не
только по документации: truck и OCCT через opencascade-rs собраны и прогнаны
зондами на одной задаче.

Ключевое из измеренного:
- Fornjot архивирован в июне 2026, целей не достиг — из кандидатов выбывает;
- opencascade-rs на Windows из коробки не собирается: три настройки окружения,
  непринятый PR #230 и четыре строки правки build.rs;
- OCCT сохраняет CYLINDRICAL_SURFACE там, где truck пишет девять сплайновых
  заплаток, и считает булевы в 4-6 раз быстрее;
- исключение к LGPL у OCCT покрывает только заголовки, не линковку;
- бесплатного входа в промышленное ядро нет ни у C3D, ни у Parasolid, ни у ACIS.

Рекомендация: OCCT через opencascade-rs, запасной вариант truck.

#5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 02:45:10 +03:00
NotBigGhostandClaude Opus 5 dd5f519c06 CLAUDE.md вне версионирования: один файл на все ветки
Файл описывает всё дерево целиком, а ветки содержат разные его части
(порт на Rust, исследование CFD, базовый C++-редактор). Отслеживаемая
копия при каждом переключении подменялась бы версией своей ветки, тогда
как нужна одна общая. Содержимое на всех ветках было идентично, так что
расхождений открепление не теряет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9Gcr11JJJyf8NnWsXjDzZ
2026-09-06 16:25:03 +03:00
3 changed files with 469 additions and 202 deletions
+3
View File
@@ -53,3 +53,6 @@ docs/theory/solver_2x_sdf/out/
.DS_Store
Thumbs.db
desktop.ini
# Общие заметки Claude Code: один файл на все ветки, вне версионирования
/CLAUDE.md
-202
View File
@@ -1,202 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**SimVulcan** is a minimal **Vulkan 1.3 / C++20** 3D model editor. It renders a
3D editor space — three reference grid planes (XY, XZ, YZ) through the origin plus
coloured X/Y/Z axes — viewed through an orbit camera, and loads/displays `.obj`
models with selectable display modes (solid, wireframe, solid + wireframe).
The C++ side runs no simulation: it is a focused rendering skeleton with an ImGui
interface (a Viewport control panel and a Mesh load panel). The repository *also*
carries an unrelated Python research prototype under `docs/theory/` — see
[Research prototype](#research-prototype-docstheory) below; it is not part of the
CMake build.
## Build
Prerequisites: **Vulkan SDK 1.3.290+** (provides `glslangValidator` for offline
shader compilation), **CMake 3.26+**, **Ninja**, a C++20 compiler (MSVC 19.36+,
gcc 11+, clang 14+). On macOS, Vulkan is via MoltenVK (Apple Silicon only).
```sh
cmake --preset windows-msvc-release
cmake --build --preset windows-msvc-release
```
Presets: `windows-msvc-debug`, `windows-msvc-release`, `linux-gcc-release`,
`linux-clang-release`, `macos-arm64-release` (all Ninja, one dir per preset under
`build/<presetName>/`). The first configure fetches dependencies via FetchContent
(GLFW, GLM, volk, vk-bootstrap, VulkanMemoryAllocator, Dear ImGui, spdlog,
tinyobjloader, Catch2) and needs network access. `VK_NO_PROTOTYPES` is set
project-wide; Vulkan entry points load through **volk**.
Warnings come from `simv_set_warnings` (`/W4 /permissive-`, or
`-Wall -Wextra -Wpedantic -Wshadow -Wold-style-cast …`); they are **not** errors.
`cmake/Sanitizers.cmake` defines `simv_enable_sanitizers` (Debug-only ASan/UBSan)
but no target currently calls it — wire it in manually when chasing memory bugs.
## Running
The executable resolves SPIR-V relative to the working directory (`FindSpvPath`
probes `spirv/<rel>` and `current_path()/spirv/<rel>`). The `SimVulcan` POST_BUILD
step copies the compiled `spirv/` tree and `assets/` next to the executable, so
**run from the executable's own directory** (`build/<preset>/src/app/`). `main.cpp`
also probes several `../` ancestors for `assets/meshes`. Meshes load at runtime
through the ImGui Mesh panel.
Running writes two files into the CWD: `pipeline_cache.bin` (serialised
`VkPipelineCache`, reloaded on the next start) and ImGui's `imgui.ini`. Both are
disposable — delete them if pipeline creation or the panel layout misbehaves.
`ContextOptions::enableValidation` / `enableDebugUtils` default to **true** in
every build config, so the Khronos validation layer is requested even in Release;
messages (error + warning severity) go through spdlog.
`run.bat` at the repo root is **stale**: it launches
`build/vs2022/src/app/Release/SimVulcan.exe`, a path the Ninja presets never
produce. Do not point users at it without fixing the path first.
## Tests
Catch2 unit tests, pure CPU/math (mesh bounds + welding). No GPU required.
```sh
cmake --build --preset windows-msvc-debug --target simv_tests
ctest --preset windows-msvc-debug
```
`windows-msvc-debug` is the only preset with a `testPreset` — for the other
configs, invoke `ctest` in `build/<preset>/` directly.
Single test / subset — either through CTest (each `TEST_CASE` is registered
individually by `catch_discover_tests`):
```sh
ctest --preset windows-msvc-debug -R "WeldVertices" -V
```
or by running the binary with a Catch2 name or tag filter:
```sh
./build/windows-msvc-debug/tests/simv_tests.exe "[decimator]"
./build/windows-msvc-debug/tests/simv_tests.exe --list-tests
```
## Architecture
### Library / target map
```
SimVulcan (exe) → simv_core, simv_vk, simv_mesh, simv_editor
simv_core → simv_vk (App owns Window + vk::Renderer; no Vulkan calls)
simv_editor → simv_core, simv_mesh (Camera, input, ImGui panels; no Vulkan)
simv_mesh → simv_core (CPU mesh only; no Vulkan)
simv_vk → third-party (volk, vk-bootstrap, VMA, GLFW, GLM, spdlog, imgui)
simv_shaders → glslangValidator (GLSL → SPIR-V)
```
Namespaces follow directories: `simv::core`, `simv::vk`, `simv::mesh`,
`simv::editor`. Each library exports `src/` as its include root, so includes are
written module-qualified (`#include "mesh/Mesh.h"`, `#include "vk/Renderer.h"`).
### Frame loop
`main.cpp` creates `core::App`, which owns the `core::Window` and a
`vk::Renderer`, then runs the loop. Each frame `Renderer::DrawFrame` calls the UI
callback (between ImGui NewFrame/Render), then records the scene: grid + mesh into
one dynamic-rendering pass with a depth attachment, followed by ImGui, then
presents (2 frames in flight, sync2 submits).
`main.cpp` wires the editor via the UI callback: it draws the panels, applies
mouse input to the `editor::Camera`, and pushes the resulting state into the
renderer (`SetViewProj`, `SetRenderMode`, `SetGridVisible`). Mesh loads go through
`MeshLoadPanel`'s callback → `Renderer::SetMeshCpu`.
### Mesh load path
`MeshLoadPanel` lists `*.obj` in the mesh directory and kicks off
`mesh::LoadObjAsync` (worker thread, `std::future`). The future is **drained on
the main thread** at the top of `MeshLoadPanel::Draw`, so the `OnLoaded` callback
— and therefore the GPU upload — always runs on the render thread. Loader
exceptions surface as the panel's status string.
`main.cpp`'s `OnLoaded` welds the mesh (`WeldVertices`, tolerance `1e-4`), logs
the counts, uploads via `SetMeshCpu`, and reframes the camera to the bbox radius.
Despite the file name, `mesh/MeshDecimator.h` implements **only** spatial-hash
vertex welding (collapse near-duplicates, drop degenerate triangles, recompute
bounds) — there is no LOD/decimation.
### Non-obvious invariants (read before editing)
- **All Vulkan lives in `src/vk/`.** `core/`, `mesh/`, `editor/` and `app/` make no
Vulkan API calls. `vk::Renderer`'s public header is deliberately Vulkan-free
(pImpl + glm/`RenderMode` only) so `App` can own it without pulling in volk. Keep
it that way — do not leak `Vk*` types into the public interfaces of those modules.
- **Single render pass with depth.** Scene (grid + mesh) and ImGui draw into one
`vkCmdBeginRendering` pass that has both a colour and a `D32_SFLOAT` depth
attachment. The ImGui backend is initialised with `depthAttachmentFormat` set so
its pipeline matches the pass; the depth buffer is recreated with the swapchain.
- **Wireframe needs `fillModeNonSolid`.** `MeshRenderer` builds a fill pipeline and
a `VK_POLYGON_MODE_LINE` pipeline; the line pipeline uses a small depth bias so
the overlay sits on top of the fill. The device feature is requested in `Context`.
Culling is off (`VK_CULL_MODE_NONE`) — loaded models may have mixed winding.
- **Camera is fixed on the origin.** `editor::Camera` orbits (yaw/pitch/distance)
the world origin; loaded models are recentred there via a translate-only model
matrix. Projection uses Vulkan clip space (`GLM_FORCE_DEPTH_ZERO_TO_ONE` + Y flip,
isolated to `Camera.cpp`).
- **Buffers.** `vk::GpuMesh` owns the model's vertex/index buffers (staged upload on
the transfer queue). `GridRenderer` builds a static host-visible line buffer once.
Both scene renderers use only a push constant — no descriptor sets.
- **Push-constant layout is a cross-file contract.** `MeshRenderer.cpp`'s anonymous
`MeshPC { mat4 mvp; vec4 color; }` must stay byte-identical to the
`push_constant` block in `mesh.vert`/`mesh.frag`; `color.a` is a *flag*, not
alpha (1 = flat-shaded, 0 = constant colour for wireframe). `GridRenderer` pushes
a bare `mat4` (vertex stage only). Change either side and you must change both.
- **Swapchain recreation rebuilds sync objects.** `RecreateSwapDependent` recreates
the per-frame `imageAvailable` semaphores (a failed acquire can leave one
signalled) *and* the per-swapchain-image `renderFinished` semaphores (the image
count may change), then re-ensures both pipelines. Keep that ordering if you
touch resize handling.
- **Mesh upload stalls the device.** `SetMeshCpu`/`ClearMesh` call `WaitIdle`
before touching `GpuMesh` — acceptable because loads are rare; do not copy that
pattern into per-frame paths.
### Shaders
GLSL under `shaders/editor/` (`mesh.{vert,frag}`, `grid.{vert,frag}`). `simv_shaders`
compiles each to `build/<preset>/spirv/editor/<name>.spv` targeting `vulkan1.3`,
with `shaders/` as the `-I` root (so `#include "common/foo.glsl"` would resolve).
Adding a shader under that globbed dir is picked up automatically
(`CONFIGURE_DEPENDS`). `mesh.frag` reconstructs a flat normal from screen-space
derivatives, so the vertex stream carries only positions (tight `float3`, one
binding, one attribute).
## Logging
`simv::core::Logger` (spdlog-backed, singleton) with category-based throttling.
Log via `LogFmt(LogCategory, LogLevel, fmt, args...)`. Categories: `Core`,
`Vulkan`, `MeshIO`, `UI`, `Test`. Per-category level, throttle interval and
on/off are settable at runtime (`SetMinLevel` / `SetThrottle` / `SetEnabled`).
Some low-level Vulkan code still calls `spdlog::` directly.
## Research prototype (`docs/theory/`)
Separate from the C++ application and from the CMake build: a Python **Lattice
Boltzmann (D2Q9, KBC-N1)** research codebase — cylinder flow with a ×2 nested AMR
patch and SDF+Bouzidi boundaries. **GPU/CuPy only, no CPU fallback.** Its
documentation and the running experiment log are in Russian.
- `docs/theory/solver_2x_sdf/` — the component-split solver (`run.py`,
`run_blockage.py`, `run_factors.py`); its `README.md` is the authoritative
status/experiment log and maps every file to the physics it owns.
- `docs/theory/demos_gpu/` — demo runs that produce the notebook's figures/GIFs;
they import physics from `solver_2x_sdf`, never duplicate it.
- `docs/theory/kbc_lbm.ipynb` — the write-up; it embeds pre-rendered artefacts and
does not execute the simulations.
- `docs/origins/` — source PDFs behind the theory.
Do not fold this into the CMake build, and do not treat it as dead code — it is
active research with a documented experiment history.
+466
View File
@@ -0,0 +1,466 @@
# Геометрические ядра САПР: что доступно и что брать
Документ по задаче #5. Цель — выбрать геометрическое ядро, поверх которого строится
САПР-часть CFDManager, и обосновать выбор, а не объявить его.
Сейчас в дереве нет ничего, что можно было бы назвать ядром: `simv_mesh` читает `.obj`
через tinyobjloader, сваривает вершины и считает AABB. Ни B-rep, ни NURBS, ни булевых
операций, ни параметрических форматов. Всё это придётся взять снаружи — своё ядро
пишется годами и не является предметом этого проекта.
## Требования и как они проверялись
Пять требований заданы в задаче, шестое добавлено здесь, потому что без него выбор
некорректен.
| № | Требование | Как проверялось |
|---|---|---|
| 1 | Лёгкость и оптимизированность | сборка и запуск на этой машине, вес артефактов |
| 2 | Rust-стек или доступное для Rust API | наличие крейта/биндингов, собрал и запустил |
| 3 | Базовое редактирование 3D (аналог Plasticity) | наличие булевых, фасок, скруглений, выдавливания |
| 4 | NURBS | какие поверхности представимы, что уходит в STEP |
| 5 | Стандартные параметрические и полигональные форматы | STEP/IGES на запись **и чтение**, STL/OBJ/glTF |
| 6 | **Лицензия** | текст лицензии и то, что она требует при поставке продукта |
Шестое требование добавлено по следующей причине. Ядро — не библиотека утилит, его
нельзя заменить за неделю; лицензионное условие, замеченное после того, как на ядре
написан год работы, стоит переписывания продукта. Поэтому GPL-ядра и ядра
с некоммерческой лицензией разбираются здесь наравне с техникой, а не в сноске.
Измерения выполнены на машине разработки: Windows 11, MSVC 19.37.32825 (VS 2022
Community), CMake 4.3.2, Ninja 1.13.2, Rust 1.97.1, сборки release. Числа, взятые
из внешних источников, помечены явно.
---
## Группа 1. Открытый исходный код
### Open CASCADE Technology (OCCT)
Единственное зрелое открытое B-rep/NURBS ядро промышленного класса. Версия **8.0.1**
от 30 июля 2026; семь модулей (Foundation Classes, Modeling Data, Modeling Algorithms,
Visualization, Data Exchange, Application Framework, Mesh).
**Форматы:** STEP (AP203, AP214, AP242), IGES до 5.3, STL, OBJ, glTF 2.0, VRML 1.0 —
на чтение и запись. ACIS, Parasolid, DXF, IFC, JT — **только через платные компоненты**
Open Cascade, в открытую поставку не входят.
**Лицензия — LGPL 2.1 с исключением, и исключение не то, за которое его обычно
принимают.** Полный текст `OCCT_LGPL_EXCEPTION.txt` разрешает ровно одно: включать
в объектный код материал из заголовочных файлов библиотеки (инлайн-функции, шаблоны,
CDL-классы) и распространять такой объектный код на своих условиях — при заметном
уведомлении, что продукт использует OCCT. Про линковку исключение не говорит **ничего**.
Практический вывод: динамическая линковка — штатный путь, обязанности сводятся
к уведомлению и к тому, что пользователь может подменить библиотеку. Статическая
линковка остаётся под LGPL 2.1 §6 и требует выдать объектные файлы приложения, чтобы
пользователь мог пересобрать его с изменённым OCCT. Это важно, потому что
`opencascade-rs` со своим режимом `builtin` собирает OCCT **статически**
(`BUILD_LIBRARY_TYPE=Static` в вызове CMake) — то есть самый удобный путь сборки
оказывается как раз тем, который для проприетарной поставки неудобен.
Динамический путь существует: без `builtin` обвязка ищет **предустановленную** OCCT
и, если та собрана с `BUILD_SHARED_LIBS=ON`, линкуется к ней как `dylib`
(`crates/opencascade-sys/build.rs:43`). Но там же, строкой ниже, стоит жёсткая
проверка версии: требуется мажор ровно **7** и минор не ниже 8, иначе сборка
паникует. Поставить в системе свежую OCCT 8.0.1 и слинковаться с ней **не получится** —
обвязка её отвергнет.
### opencascade-rs — путь к OCCT из Rust
Крейты `opencascade` / `opencascade-sys` версии 0.3.0 (обновлены 24 августа 2026),
биндинги через `cxx`, лицензия LGPL-2.1. Форк `bschwind` живой, 263 звезды; форк
`mkovaxx` последний раз трогали в октябре 2025 — его рассматривать не стоит.
API высокоуровневый и на вид приятный:
```rust
let my_box = Shape::box_with_dimensions(10.0, 10.0, 1.0);
let another = Shape::box_with_dimensions(1.0, 1.0, 0.8);
my_box.subtract(&another).chamfer(0.07)
```
Есть `write_step` / `write_iges` / `write_stl`, `read_step`, `mesh()`
с настраиваемым допуском, фаски, скругления, выдавливание, заметание по траектории.
**Существенное отставание:** `occt-sys` зафиксирован на OCCT **7.8.1**, тогда как
апстрим уже 8.0.1. Это не косметика — между ними мажорный релиз, и всё, что исправлено
в 7.9 и 8.0, в этом пути недоступно, пока обвязку не обновят. Причём 8.0 заявлен
командой OCCT3D (Capgemini Engineering) как релиз с ломающими изменениями API,
чисткой интерфейсов и переработкой булевых операций, тесселяции и обработки допусков —
то есть обновление обвязки будет не механическим, и ждать его быстро не стоит.
### truck — чистый Rust
Единственный живой чисто-растовый B-rep/NURBS кернел. Apache-2.0, 1559 звёзд,
коммиты в репозитории вплоть до 7 сентября 2026 — **но крейты на crates.io не
обновлялись с 20 сентября 2024** (`truck-modeling` 0.6.0). Два года git-активности
без релиза — характерный признак: пользоваться придётся либо старым релизом,
либо git-зависимостью.
Умеет: NURBS-геометрию, топологию (вершины/рёбра/грани/оболочки/тела), булевы операции
(`truck-shapeops`), тесселяцию (`truck-meshalgo`), чтение и запись STEP
(`truck-stepio`, есть и `in`, и `out`), полигональные форматы через `truck-polymesh`.
### Остальные открытые — отсев с причиной
| Проект | Состояние | Почему не подходит |
|---|---|---|
| **Fornjot** | архивирован 19 июня 2026 | Автор закрыл проект: «This project has been shut down. Its goals were never reached», код read-only, пригоден лишь для «very simple models» |
| **SolveSpace** | живой, 4163★, GPL-3.0 | GPLv3 несовместим с проприетарной поставкой; NURBS-булевы, по признанию самих авторов, медленные, ограничены поверхностями выдавливания и вращения и «not too robust even then» |
| **BRL-CAD** | живой, 1029★ | CSG-ориентированное ядро с Tcl-инфраструктурой, не про интерактивное NURBS-моделирование |
| **openNURBS** | живой, 562★ | Чтение/запись `.3dm` и NURBS-геометрия, **но моделирования нет**: ни булевых, ни фасок |
| **curvo** | живой, MIT, 0.1.91 | NURBS-кривые и поверхности как математика, не B-rep ядро |
| **Manifold** | живой, Apache-2.0, есть крейт `manifold3d` 0.4.1 (обновлён 9 сентября 2026) | Быстрые и надёжные булевы, но по **полигональным сеткам**; NURBS и B-rep отсутствуют как понятие. Полезен позже — для подготовки геометрии к расчётной сетке, не как ядро |
| **CGAL** | живой | Вычислительная геометрия, не САПР-ядро; ядро GPL с платной альтернативой |
Отдельно стоит отметить `libslvs` из SolveSpace: это **решатель геометрических
ограничений**, то есть ровно та подсистема, которой нет ни у OCCT, ни у truck.
К выбору ядра он отношения не имеет, но к параметрике — прямое (см. раздел о том,
что придётся писать самим).
---
## Группа 2. Закрытый код, бесплатные и некоммерческие лицензии
Задача изначально говорила про открытые ядра; рамка расширена владельцем проекта.
Результат этой группы отрезвляющий: **бесплатного входа в промышленное ядро нет ни
у одного из четырёх.**
### C3D Modeler (C3D Labs)
Единственное коммерческое геометрическое ядро российской разработки, одно из пяти
лицензируемых ядер в мире. B-rep с NURBS, обмен со STEP, IGES, Parasolid, ACIS,
полигональными форматами. API — C++ с C-обёрткой; Rust-биндингов нет, их пришлось бы
писать самим.
Публично доступна **только 90-дневная пробная версия** с техподдержкой. Бесплатной
некоммерческой или академической лицензии в открытом доступе найти не удалось:
у компании есть образовательные партнёрства с вузами, хакатоны и стажировки, но
лицензия под них публично не описана и выдаётся по договорённости. Условия
коммерческой лицензии компания называет гибкими и определяет индивидуально; цифра
в 1 750 000 ₽ в год за лицензию разработчика плюс роялти встречается во вторичных
источниках и **здесь не проверена** — уточнять её нужно у самой C3D Labs.
### Parasolid (Siemens)
Ядро, на котором построена названная в задаче Plasticity, а также SolidWorks, NX и
десятки других систем. Эталон по надёжности булевых и качеству поверхностей.
Лицензируется коммерчески; публичных условий, прайса, бесплатного или академического
тарифа в открытом доступе нет — вход только через переговоры с Siemens, и по отзывам
разработчиков процесс и суммы для небольших проектов оказываются заградительными.
Rust-биндингов нет.
### 3D ACIS Modeler и CGM Modeler (Spatial / Dassault Systèmes)
Второе историческое коммерческое ядро (ACIS, с конца 1980-х) и ядро CATIA (CGM).
Технически полноценные B-rep/NURBS ядра. Ситуация с лицензией та же, что у Parasolid:
публичных бесплатных или академических условий нет, пробный доступ и академические
программы — по запросу. Rust-биндингов нет.
### Zoo Design API (бывш. KittyCAD)
Самый близкий к требованию 2 вариант: проприетарное ядро, **написанное на Rust**,
GPU-native, выдаёт настоящий B-rep. Есть официальный Rust-клиент `kittycad.rs`,
открытые Design Studio и язык KCL.
Но это **облачный сервис**, а не библиотека: геометрия считается на их серверах,
тарификация поминутная, бесплатно даётся 20 минут API (баланс $10), дальше
pay-as-you-go. Для настольного САПР с интерактивным редактированием это означает
зависимость от сети, задержку на каждую операцию и оплату за использование, которая
растёт вместе с числом пользователей. Для проекта, который сам по себе считает
гидродинамику локально, такая схема противоречива.
### Обмен форматами отдельно
CAD Exchanger SDK, HOOPS Exchange, ODA — не ядра моделирования, а конвертеры.
Они закрывают требование 5 там, где ядро не справляется (DWG, JT, CATIA, NX), и
имеют смысл как **дополнение** к выбранному ядру, если такие форматы понадобятся.
Все три коммерческие. Для OCCT, который сам читает STEP и IGES, необходимости
в них на текущем этапе нет.
---
## Сводная таблица
Обозначения: ● полностью, ◐ частично, ○ нет.
| Кандидат | 1. Лёгкость | 2. Rust | 3. Редактирование | 4. NURBS | 5. Форматы | 6. Лицензия и её риск |
|---|---|---|---|---|---|---|
| **OCCT + opencascade-rs** | ○ 2,7 ГБ сборки, exe 19,4 МБ | ◐ биндинги есть, но на Windows не собираются без патчей | ● булевы, фаски, скругления | ● полноценно, аналитика сохраняется | ● STEP/IGES/STL/OBJ/glTF | LGPL-2.1+искл.: динамическая линковка — ок, статическая обязывает |
| **truck** | ● 98 с сборки, exe 1,6 МБ | ● родной, собрался с первой попытки | ◐ булевы есть (в 4–6 раз медленнее), набор беднее | ◐ есть, но аналитика уходит в B-сплайны | ◐ STEP r/w, полигоны; IGES нет | Apache-2.0 — риска нет |
| Fornjot | — | ● | ○ | ○ | ○ | архивирован |
| SolveSpace | ● | ○ | ◐ | ◐ слабые булевы | ◐ | **GPL-3.0 — стоп для проприетарного** |
| openNURBS | ● | ○ | ○ | ● | ◐ только 3dm | своя, свободная |
| Manifold | ● | ● крейт `manifold3d` | ◐ только сетки | ○ | ◐ | Apache-2.0 — риска нет |
| C3D Modeler | ◐ | ○ писать самим | ● | ● | ● | 90 дней, дальше платно |
| Parasolid | ◐ | ○ | ● эталон | ● | ● | платно, условий публично нет |
| ACIS / CGM | ◐ | ○ | ● | ● | ● | платно, условий публично нет |
| Zoo Design API | ● клиент лёгкий | ● родной Rust | ● | ● | ● | облако, поминутная оплата |
---
## Измерения
Всё в этом разделе измерено здесь, а не переписано из чужих статей.
### truck
Зонд: два куба → булево объединение → тесселяция → экспорт OBJ и STEP. Написан
по документации и **скомпилировался с первой попытки без правок** — показатель того,
что API соответствует документации.
| Что | Значение |
|---|---|
| холодная сборка release | **98,6 с** |
| пакетов в графе зависимостей | 103 |
| исходники самих крейтов truck | 1,9 МБ, 233 файла, 10 крейтов |
| размер `target/` | 446,8 МБ |
| исполняемый файл зонда | 1,60 МБ |
| построение двух кубов | 116 мкс |
| булево объединение (плоскости) | 1,72 мс |
| тесселяция результата | 340 мкс, 60 вершин / 36 граней |
| экспорт STEP | 445 мкс, 17,6 КБ |
| экспорт OBJ | 637 мкс, 8,8 КБ |
Второй зонд — на кривых поверхностях: цилиндр (через вращательное заметание) против
куба, пересечение и объединение.
| Что | Значение |
|---|---|
| булево пересечение цилиндр ∩ куб | **32,6 мс** (в 19 раз дороже плоскостного) |
| булево объединение цилиндр ∪ куб | 29,5 мс |
| результат тесселируется | да, 84 вершины |
Третий зонд — чтение STEP обратно: файл, записанный первым зондом, разобран за
**11,1 мс**, из него восстановлены 1 оболочка, 12 граней и 18 плоскостей. То есть
круг «записал — прочитал» у truck замкнут. Мелочь, характеризующая зрелость API:
`Table::from_step` возвращает `Option`, а не `Result`, поэтому причина неудачного
разбора вызывающему не сообщается вовсе.
Обе булевы операции отработали — это лучше, чем можно было ожидать от ядра такой
зрелости. Но экспорт цилиндра в STEP показал существенное: файл содержит `B_SPLINE_SURFACE` и
`PLANE`, и **не содержит `CYLINDRICAL_SURFACE`**. То есть аналитические поверхности
truck не сохраняет, всё сводится к сплайнам. Файл валиден, но принимающая система
получает вместо цилиндра сплайновую аппроксимацию: хуже для распознавания элементов,
хуже для точности, тяжелее.
### OCCT через opencascade-rs
Сборка этого пути оказалась мерой сама по себе — она упёрлась в три препятствия
подряд, и каждое стоит знать заранее.
1. **CMake 4.x против OCCT 7.8.1.** `occt-sys` собирает OCCT 7.8.1, чей
`cmake_minimum_required` объявляет совместимость ниже 3.5. В CMake 4.3.2 такая
совместимость удалена, и конфигурация падает сразу:
«Compatibility with CMake < 3.5 has been removed from CMake». Обходится
переменной окружения `CMAKE_POLICY_VERSION_MINIMUM=3.5`. В OCCT 8.0.1 этой
проблемы нет — но обвязка на него не переведена.
2. **Генератор Visual Studio.** Крейт `cmake` по умолчанию берёт генератор
«Visual Studio 17 2022», который на этой машине не отработал: установлены
и Build Tools 2026, и VS 2022 Community. Лечится `CMAKE_GENERATOR=Ninja`
из среды `vcvars64.bat`.
3. **Длина пути Windows.** Дерево OCCT глубокое, и в каталоге с длинным путём
сборка предупреждает про `CMAKE_OBJECT_PATH_MAX` (187 символов при пределе 250)
и падает. Понадобился короткий путь вида `C:\occprobe`.
После того как все три обойдены, сборка идёт и **само ядро OCCT собирается успешно**.
А потом падает компиляция cxx-мостов — тремя однотипными ошибками в собственных
заголовках обвязки:
```
b_rep.hxx(16): error C2440: невозможно преобразовать
"opencascade::handle<Geom_Surface> *" в "std::unique_ptr<Handle_Geom_Surface, ...>"
```
| Что | Значение |
|---|---|
| время до провала | **36 мин 09 с** (01:59:43 → 02:35:52) |
| объём `target/` к этому моменту | ~1,0 ГБ |
| исходники OCCT (`occt-sys` 7.8.1) | 87 МБ, 14 984 файла; скачиваемый архив 13,7 МБ |
| ошибок компиляции | 3, все в `b_rep.hxx` |
| собралось ли ядро OCCT | **да** |
| собрались ли биндинги | **нет** |
Причина — коллизия имён: обвязка объявляет собственные типы `Handle_Geom_Surface`
и им подобные, которые под MSVC сталкиваются с одноимёнными типами самого OCCT.
Это **известная незакрытая проблема проекта**, а не особенность этой машины:
PR #196 «Handle name conflict to fix Windows build» был влит ещё в январе 2025,
PR #230 «Fix MSVC handle name collisions» от 19 августа 2026 — **закрыт, не влит**,
а PR #216 «Fix Windows build» открыт с октября 2025 и всё ещё не принят.
Проверка обхода: патч из непринятого PR #230 (переименование `Handle_*` →
`RustHandle_*`, 26 файлов) применяется к текущему master чисто — `git apply` проходит
без конфликтов. После него мосты компилируются, **но сборка падает уже на линковке**:
23 неразрешённых символа, среди них `InitializeAcl` и `InitializeSecurityDescriptor`,
то есть не подключены системные библиотеки Windows. Это ровно предмет второго,
до сих пор **открытого** PR #216 — и наложить его поверх первого не получается,
два непринятых патча конфликтуют между собой в `build.rs`. Понадобилось дописать
четыре строки вручную:
```rust
println!("cargo:rustc-link-lib=dylib=advapi32");
println!("cargo:rustc-link-lib=dylib=gdi32");
println!("cargo:rustc-link-lib=dylib=shell32");
println!("cargo:rustc-link-lib=dylib=comdlg32");
```
**После этого всё собралось и заработало.** Итого, чтобы получить рабочий
OCCT-из-Rust на Windows, потребовались: три настройки окружения, один непринятый
PR и четыре строки своей правки.
| Что | Значение |
|---|---|
| холодная сборка до провала (без патчей) | 36 мин 09 с |
| пересборка после патчей (ядро уже собрано) | 1 мин 45 с |
| исходники OCCT (`occt-sys` 7.8.1) | 87 МБ, 14 984 файла; архив 13,7 МБ |
| размер `target/` | ~2,7 ГБ |
| исполняемый файл зонда | **19,4 МБ** (против 1,60 МБ у truck) |
Работа зонда: куб 10×10×10 минус куб 6×6×6, фаска 0,5, тесселяция, три экспорта
и чтение STEP обратно.
| Операция | Время |
|---|---|
| два примитива | 503 мкс |
| булево вычитание | 7,46 мс |
| фаска по всем рёбрам | 20,2 мс |
| тесселяция | 12,1 мс, 174 вершины / 86 треугольников |
| экспорт STEP | 25,4 мс, 110,8 КБ, 2378 сущностей |
| экспорт IGES | 14,6 мс, 87,3 КБ |
| экспорт STL | 15,2 мс, 22,4 КБ |
| чтение STEP обратно | 48,4 мс, те же 174 вершины |
| всего | 145 мс |
### Прямое сравнение на одной задаче
Чтобы сравнение было честным, обоим ядрам дана одна и та же работа: цилиндр
радиуса 2 высоты 4, пересечение и объединение с кубом 1,5, затем запись цилиндра
в STEP.
| | truck | OCCT |
|---|---|---|
| цилиндр ∩ куб | 32,6 мс | **7,2 мс** |
| цилиндр ∪ куб | 29,5 мс | **4,7 мс** |
| тесселяция результата | 84 вершины | 38 вершин |
| STEP цилиндра, размер | 13 281 байт | **5 652 байта** |
| боковая поверхность в STEP | 9 × `B_SPLINE_SURFACE` | **1 × `CYLINDRICAL_SURFACE`** |
Последняя строка — самая содержательная во всём документе. Там, где OCCT пишет
одну аналитическую цилиндрическую поверхность, truck пишет девять сплайновых
заплаток: файл вдвое толще, а принимающая система видит не цилиндр, а его
аппроксимацию. Булевы операции при этом у OCCT в 4–6 раз быстрее.
Мелкая, но заметная деталь эксплуатации: OCCT при записи STEP печатает в stdout
собственную статистику трансфера («Statistics on Transfer», рамки из звёздочек).
В библиотеке, встраиваемой в приложение с интерфейсом, этот вывод придётся глушить.
---
## Рекомендация
**Основной вариант — Open CASCADE Technology, из Rust через `opencascade-rs`.**
Причины, по порядку веса:
1. **Это единственное открытое ядро, которое закрывает требования 3, 4 и 5
целиком.** Всё остальное открытое либо не умеет моделировать (openNURBS, curvo,
Manifold), либо умеет плохо и под GPL (SolveSpace), либо закрыто (Fornjot).
2. **Требование 2 выполняется без написания биндингов.** `opencascade-rs` даёт
готовый Rust-API с булевыми, фасками, тесселяцией и обменом форматами.
3. **Требование 5 закрывается из коробки:** STEP AP242 и IGES на чтение и запись —
это то, чем САПР обменивается с внешним миром, и ни truck, ни полигональные
библиотеки этого в таком объёме не дают.
4. Зрелость: ядро развивается с 1990-х; на нём построены FreeCAD (его B-rep/NURBS
слой — это OCCT), CadQuery и 3D-часть KiCad.
Цена решения, названная честно и **измеренная**: ядро тяжёлое (87 МБ исходников,
2,7 ГБ сборочного каталога, 19,4 МБ исполняемый файл против 1,6 МБ у truck), это
C++ с версией 7.8.1 в текущей обвязке, а сама обвязка **на Windows из коробки не
собирается**: понадобились три настройки окружения, патч из непринятого PR #230 и
четыре строки правки `build.rs`. Плюс лицензия, обязывающая к динамической линковке
при проприетарной поставке — а динамический путь требует системной OCCT ветки 7.x.
Это не аргумент против выбора, но это работа, которую придётся сделать до того, как
будет написана первая строка САПР-логики, и её лучше запланировать явно. Разумный
первый шаг после утверждения — **форк `opencascade-rs`** с наложенными исправлениями:
иначе каждый разработчик проекта будет проходить этот путь заново, а обновление
апстрима будет ломать сборку.
**Запасной вариант — truck**, и он не запасной «на всякий случай», а по конкретному
сценарию: если проект останется целиком на Rust и согласится на собственную
реализацию недостающего. truck легче, собирается за полторы минуты, не тянет C++
и не создаёт лицензионных обязанностей вовсе (Apache-2.0). Но за это придётся
заплатить отсутствием IGES, потерей аналитических поверхностей при экспорте,
более скудным набором операций и работой с крейтами двухлетней давности либо
git-зависимостью.
**Отдельно про требование 3.** В задаче эталоном названа Plasticity — она построена
на Parasolid, и это надо понимать буквально: уровня её прямого моделирования
(push/pull по грани с мгновенной перестройкой тела) ни одно открытое ядро само по
себе не даёт. OCCT предоставляет операции, из которых такой режим собирается, но
собирать его придётся самим.
**Про закрытые ядра.** Ни одно из четырёх не даёт бесплатного пути в продукт:
C3D — 90 дней, Parasolid и ACIS/CGM — только переговоры, Zoo — поминутная оплата
и облако. Возвращаться к ним осмысленно тогда, когда у проекта появится бюджет
и станет ясно, что именно в OCCT не устраивает: менять ядро на этапе прототипа
дороже, чем позже, когда требования известны точно.
---
## Что придётся писать самим поверх любого ядра
Это не недостаток конкретного кандидата, а общее свойство: ядро даёт геометрию,
а не САПР.
- **Решатель геометрических ограничений.** Ни OCCT, ни truck его не содержат.
Без него нет параметрического эскиза. Кандидаты: `libslvs` из SolveSpace
(GPL-3.0 — то же лицензионное ограничение, что и у самого SolveSpace),
либо собственная реализация.
- **Дерево построения и история операций.** Ядро выполняет операции, но не помнит,
в каком порядке и от чего они зависели.
- **Топологические имена.** Устойчивая идентификация граней и рёбер между
перестроениями — классическая трудная задача, ядром не решается.
- **Связь с расчётной частью.** Для CFD нужна сетка, а не B-rep: понадобится
контролируемая тесселяция и, вероятно, воксельное или SDF-представление,
согласованное с решателем KBC.
---
## Что нужно от владельца проекта
1. **Утвердить ядро** — критерий готовности задачи #5 сформулирован именно так.
2. **Решить вопрос линковки**, если продукт будет проприетарным: удобный путь
(`builtin`) даёт статическую сборку OCCT, а динамический требует предустановленной
в системе OCCT ветки 7.8/7.9 — версию 8.x обвязка отвергает. Выбор: принять
обязанности LGPL 2.1 §6, держать системную 7.x, либо править обвязку под себя.
3. **Согласиться на форк `opencascade-rs`** (или явно отказаться): без него сборка
на Windows воспроизводится только вручную, по шагам из раздела измерений.
4. Если решено смотреть на закрытые ядра всерьёз — **запросить условия** у C3D Labs
(90-дневная пробная плюс индивидуальные условия) и у Siemens по Parasolid.
Регистрация и подписание — действия владельца, агентом они не выполняются.
---
## Источники
- [Open CASCADE Technology — обзор и форматы](https://occt3d.com/dev/doc/overview/html/index.html)
- [FOSDEM 2026: OCCT3D 8.0 — ломающие изменения и развитие ядра](https://archive.fosdem.org/2026/schedule/event/QQRAAF-occt3d-8-kernel-evolution/)
- [OCCT_LGPL_EXCEPTION.txt — текст исключения](https://raw.githubusercontent.com/Open-Cascade-SAS/OCCT/master/OCCT_LGPL_EXCEPTION.txt)
- [Релизы OCCT (8.0.1, 30.07.2026)](https://github.com/Open-Cascade-SAS/OCCT/releases)
- [opencascade-rs](https://github.com/bschwind/opencascade-rs)
- [opencascade-rs PR #230 «Fix MSVC handle name collisions» — закрыт, не влит](https://github.com/bschwind/opencascade-rs/pull/230)
- [opencascade-rs PR #216 «Fix Windows build: …missing system libraries» — открыт](https://github.com/bschwind/opencascade-rs/pull/216)
- [truck — shape processing kernel by Rust](https://github.com/ricosjp/truck)
- [Fornjot — архив, заявление о закрытии](https://github.com/hannobraun/fornjot)
- [SolveSpace — использование как библиотеки](https://solvespace.com/library.pl)
- [openNURBS](https://github.com/mcneel/opennurbs)
- [Manifold](https://github.com/elalish/manifold)
- [curvo](https://github.com/mattatz/curvo)
- [C3D Toolkit — лицензирование](https://c3dlabs.ru/products/c3d-toolkit/licensing/)
- [C3D Toolkit — тестовая версия](https://c3dlabs.ru/evaluation/)
- [Zoo Design API](https://zoo.dev/design-api)
- [Zoo — тарифы API](https://zoo.dev/api-pricing)
- [kittycad.rs — Rust-клиент Zoo](https://github.com/KittyCAD/kittycad.rs)