Работа со сборкой
Сборка (CADAssembly) — узел дерева модели с матрицей размещения (CADMatrix). Флаг IsDetail задаёт содержимое узла: при IsDetail = true внутри лежит деталь (CADPart) с геометрией, при IsDetail = false — вложенные сборки. Матрица есть только у CADAssembly, поэтому любой размещаемый компонент — даже одиночная деталь (винт, шайба) — оборачивается в собственную CADAssembly (она несёт его положение), а геометрия хранится внутри, в CADPart. Один и тот же CADPart может переиспользоваться многими сборками (массивы, повторяющийся крепёж). Импорт сборки — рекурсивный обход дерева сверху вниз с накоплением преобразования.
Например, так узел «Ролик в кронштейне» выглядит в дереве модели CAD-системы:
Ролик в кронштейне — CADAssembly
├─ Кронштейн — CADAssembly → CADPart
├─ Ось — CADAssembly → CADPart
├─ Ролик — CADAssembly
│ ├─ Втулка — CADAssembly → CADPart
│ └─ Обойма — CADAssembly → CADPart
├─ Винт M6 (1) — CADAssembly → CADPart
├─ Винт M6 (2) — CADAssembly → CADPart
├─ Винт M6 (3) — CADAssembly → CADPart
├─ Винт M6 (4) — CADAssembly → CADPart
├─ Шайба 6 (1) — CADAssembly → CADPart
├─ Шайба 6 (2) — CADAssembly → CADPart
├─ Шайба 6 (3) — CADAssembly → CADPart
└─ Шайба 6 (4) — CADAssembly → CADPart
Каждый узел дерева — это CADAssembly со своей матрицей; собственно геометрия появляется только в листовых CADPart внутри них. «Ролик» — вложенная подсборка (IsDetail = false). «Винт M6» и «Шайба 6» присутствуют в четырёх экземплярах — это четыре отдельные CADAssembly (четыре матрицы), ссылающиеся на одну и ту же геометрию CADPart.
В примерах ниже узел сборки (
CADAssembly) имеет поля:Name— имя (папка в дереве),Transform— матрица размещения (CADMatrix),IsDetail— флаг содержимого, и само содержимое: приIsDetail = true— детальPart(CADPart), приIsDetail = false— список дочерних узловChildren. В Варианте 2 у листовых узлов используется ещёComponentId— идентификатор исходного компонента, общий для всех одинаковых деталей (по нему определяются повторы). Конкретный состав полей зависит от вашей CAD-системы.
Что нужно воспроизвести в SGF. Импорт сборки решает три задачи: повторить структуру дерева (папки), разместить каждый компонент его матрицей и записать геометрию деталей (ComboModel / ComboSolid, см. Работа с деталью). Структуру дерева строят группы, а повторяющиеся компоненты можно переиспользовать через блоки. Ниже — оба способа; они равноправны и обычно сочетаются.
Группа. Каждый узел сборки отображается в дереве модели CAM-системы как папка. Пара методов StartGroupEntity / CloseGroupEntity открывает и закрывает такую папку: все объекты, записанные между вызовами, принадлежат ей. Вызовы вкладываются — ровно так же, как вложены узлы дерева CAD-сборки.
| Метод | Описание |
|---|---|
StartGroupEntity(EntityName: string) |
Начать группу — папку в дереве модели с именем EntityName. Все объекты, построенные до парного CloseGroupEntity, попадают в эту папку. Вызовы можно вкладывать: внутри папки создаются вложенные папки. Нельзя вызывать внутри StartComboModel — так как он формирует отдельную закрытую сшитую оболочку. |
CloseGroupEntity() |
Закрыть текущую группу (папку). |
Матрица преобразования. Положение компонента задаётся перед сохранением его геометрии. Матрица задаётся методом SetCurrentTransform2, и все последующие поверхности — до следующей смены матрицы — умножаются на неё. При обходе сборки в матрицу передаётся накопленное мировое преобразование узла (произведение матриц от корня), поэтому для каждого солида задаётся его финальное положение.
Матрицу вызывают до открытия солида — перед StartGroupEntity или StartComboModel. Внутри StartComboModel менять её нельзя: тело сшивается в собственных координатах и размещается целиком по матрице, заданной до его открытия (см. Работа с деталью).
| Метод | Описание |
|---|---|
SetCurrentTransform2(vT, vX, vY, vZ: TST3DPoint) |
Переключает матрицу преобразования для всех последующих объектов; задаётся положением (vT — начало координат) и явно заданными осями vX, vY, vZ. Нужно вызывать перед сохранением солида (перед StartGroupEntity или StartComboModel) — все его поверхности будут умножены на эту матрицу. При сохранении сборок задаётся финальная матрица для каждого солида. Нельзя вызывать внутри StartComboModel. |
SetCurrentTransform(vT, vZ, vX: TST3DPoint) |
Устарело (но в некоторых случаях ещё используется). То же, что SetCurrentTransform2, но система координат задаётся положением (vT), осью Z (vZ) и осью X (vX); ось Y вычисляется как векторное произведение vZ × vX — это неоднозначно, если vZ и vX не строго перпендикулярны. По возможности используйте SetCurrentTransform2. |
Вариант 1 — стандартный обход (группы). Самый простой подход: каждый узел сборки оборачивается в StartGroupEntity / CloseGroupEntity, геометрия детали записывается внутри. Матрица размещения накапливается по пути от корня: мировое преобразование каждого узла = преобразование родителя × локальная матрица узла.
// Рекурсивный обход сборки. parentTransform — накопленное преобразование родителя.
void SaveAssembly(CADAssembly asm, TST3DMatrix parentTransform)
{
// мировое преобразование узла = преобразование родителя × локальная матрица сборки
TST3DMatrix world = Multiply(parentTransform, ToMatrix(asm.Transform));
sgr.StartGroupEntity(asm.Name);
try
{
if (asm.IsDetail)
SavePart(asm.Part, world); // лист: деталь с геометрией
else
foreach (CADAssembly child in asm.Children)
SaveAssembly(child, world); // подсборка: спускаемся в дочерние узлы
}
finally
{
sgr.CloseGroupEntity();
}
}
Этот вариант подходит для большинства случаев. Недостаток: если один и тот же компонент встречается в сборке многократно (например, четыре одинаковых болта), его геометрия записывается в файл четыре раза. Для таких случаев предназначен Вариант 2.
Вариант 2 — блоки (Blocks). Блок — это одна модель с топологией (ComboModel) или без, записанная в SGF один раз под уникальным именем и вставляемая в дерево любое количество раз через InsertBlock. Геометрия хранится единожды, каждый экземпляр задаёт только матрицу размещения. Для компонентов, встречающихся больше одного раза, это сокращает размер файла и ускоряет загрузку.
- подсборка (
IsDetail = false) — обычная папкаStartGroupEntity/CloseGroupEntity(как в Варианте 1) с рекурсивным обходом дочерних узлов; - деталь (
IsDetail = true) — сохраняется как блок: при первом вхождении компонента его геометрия записывается в блок (StartBlock/CloseBlock) и тут же размещаетсяInsertBlock; при последующих вхождениях вызывается толькоInsertBlock. Уже записанные блоки отслеживает множествоsavedBlocks(в импортёре STEP — флагfShellSaved), предварительный подсчёт повторов не нужен.
InsertBlock сам создаёт узел в дереве модели (его аргумент NewBlockName — имя этого узла), поэтому оборачивать узел в дополнительный StartGroupEntity не нужно.
Методы блоков.
| Метод | Описание |
|---|---|
StartBlock(BlockName: string) |
Начать блок — зафиксировать под именем BlockName одну модель с топологией (ComboModel) или без для последующего переиспользования. Геометрию внутри блока записывают те же методы, что и для одиночной детали (см. Работа с деталью). Блок помещается в буфер. |
CloseBlock() |
Завершить формирование блока (он остаётся в буфере). |
InsertBlock(BlockID, NewBlockName: string) |
Скопировать блок из буфера в дерево модели. BlockID — имя блока из StartBlock; NewBlockName — имя создаваемого узла (группы) в дереве. Матрица размещения задаётся предшествующим вызовом SetCurrentTransform2. |
// savedBlocks — ComponentId уже записанных блоков (поле импортёра; аналог fShellSaved в STEP).
readonly HashSet<string> savedBlocks = new HashSet<string>();
void SaveAssemblyWithBlocks(CADAssembly asm, TST3DMatrix parentTransform)
{
TST3DMatrix world = Multiply(parentTransform, ToMatrix(asm.Transform));
if (!asm.IsDetail)
{
sgr.StartGroupEntity(asm.Name);
try
{
foreach (CADAssembly child in asm.Children)
SaveAssemblyWithBlocks(child, world);
}
finally
{
sgr.CloseGroupEntity();
}
return;
}
// лист (деталь): при первом вхождении компонента создаём блок, далее — только вставляем
if (savedBlocks.Add(asm.ComponentId))
{
sgr.StartBlock(asm.ComponentId);
try
{
SavePart(asm.Part, UnitMatrix3D);
} // геометрия в локальных координатах детали
finally
{
sgr.CloseBlock();
}
}
// размещаем экземпляр по его матрице (в том числе при первом вхождении)
sgr.SetCurrentTransform2(world.vT, world.vX, world.vY, world.vZ);
sgr.InsertBlock(asm.ComponentId, asm.Name); // InsertBlock сам создаёт узел дерева
}
Оба варианта равноправны и могут сочетаться в одном импортёре. Вариант 1 проще в реализации и отладке; Вариант 2 даёт меньший размер SGF-файла при сборках с повторяющимися деталями и загружается быстрее. В примере выше детали оформляются блоками (повторная геометрия пишется один раз и переиспользуется), а подсборки и сборочные узлы остаются папками-группами.