Autodesk Inventor API. Первые шаги: различия между версиями

Содержимое удалено Содержимое добавлено
Нет описания правки
мНет описания правки
Строка 66:
## Вставка компонента. Ориентирование компонента
## Наложение сборочных зависимостей
# [[Ссылочные ключи (Reference keys) и атрибуты (Attributes)]]
# [[Атрибуты]]
## Что это такое, организация
## AttributeSet
## [[Reference Keys]]
# [[Взаимодействие с пользователем]]
## Выделение объектов: SelectSet
Строка 100:
 
__TOC__
 
 
 
== Ссылочные ключи и атрибуты ==
 
Ссылочные ключи (Reference keys) являются средством получения особых ссылок на объекты Инвентора. Такие ссылки отличаются тем, что сохраняются между этапами редактирования модели и даже между сеансами работы Инвентора. Например, приведенный ниже программный фрагмент получает ссылку на тело и затем ссылку на грань этого тела.
 
<source lang=vbnet>
Dim oBody As SurfaceBody
Set oBody = ThisApplication.ActiveDocument.ComponetDefinition.SurfaceBodies.Item(1)
Dim oFace As Face
Set oFace = oBody.Faces.Item(1)
</source>
 
 
Это наиболее распространенный вариант ссылок на объекты, однако они являются временными. Если каким-либо образом сохранить такие ссылки, затем закрыть и снова открыть документ, выяснится, что сохраненные ссылки более не актуальны. Если ссылка указывает на объект типа B-Rep (SurfaceBody, FaceShell, Face, EdgeLoop, Edge, EdgeUse или Vertex), и вы сделали нечто, что вызвало пересчет модели, скажем, изменили значение параметра, ссылка перестанет быть актуальной и, следовательно, сколько-нибудь полезной. Для объектов типа B-Rep такие ссылки имеют смысл только, пока модель пребывает в неизменном статичном состоянии. Ссылки на прочие (не B-Rep) объекты остаются действительными и после пересчета модели, однако теряют актуальность c закрытием документа.
API предоставляет два метода, которые позволят получить более долгоживущие ссылки на любой объект внутри Инвентора. Это ссылочные ключи (reference key) и атрибуты (attribute).
 
 
=== Ссылочные ключи (Reference Keys) ===
 
Ссылочный ключ (Reference key) —это уникальный и неизменный идентификатор объекта. Reference key это не сама ссылка, но он используется для создания ссылки на тот объект, для которого reference key был сгенерирован. Он неизменный, поскольку продолжает работать в процессе пересчета модели, а также после закрытия и повторного открытия документа.
 
Другой особенностью ссылочных ключей является то, что сам Инвентор их не поддерживает. Это означает, что Инвентор не сохраняет эту информацию в своих файлах. Если вы используете ссылочные ключи, задача их сохранения между сеансами работы Инвентора ложится на вас. Использовать ссылочные ключи в пределах одной сессии Инвентора довольно просто, для этого достаточно сохранить ключ в переменной и использовать его по мере необходимости.
 
Чтобы воспользоваться ссылочными ключами в последующих сеансах Инвентора, вы вероятно сохраните их во внешнем файле и, таким образом, сможете использовать ключи в любой момент в будущем. Управление ссылочными ключами является исключительно вашей заботой. Инвентор лишь предоставляет их в ваше распоряжение, а для заданного ключа возвращает соответствующий ему объект.
 
Поскольку сам Инвентор не управляет ключами, возникает любопытный побочный эффект — вы можете использовать ссылочные ключи без изменения документа, в котором ключи были получены. Это означает, что вам не требуется сохранять документ, в котором они были сгенерированы, более того, сам документ может быть защищенным от изменений (read-only). По этой причине ссылочные ключи используются в Apprentice.
 
Техника использования ссылочных ключей для объектов типа B-Rep отличается рядом особенностей. Мы начнем с более простых не B-Rep объектов, а уже потом перейдем к B-Rep. При необходимости поддерживать одновременно B-Rep и не B-Rep объекты, вы можете для всех объектов применять единообразную технику, характерную для B-Rep, как более универсальную.
 
=== Ссылочные ключи для не B-Rep объектов ===
 
В принципе, идея ссылочных ключей довольно проста, если представлять их как идентификаторы объектов. Ссылочные ключи не B-Rep объектов еще и довольно просты в применении. Вы получаете ссылочный ключ объекта, каким-либо образом сохраняете его и позднее используете в любой момент, чтобы получить нормальную «живую» ссылку на объект. Объект ReferenceKeyManager (менеджер ссылочных ключей) в документе с нужными вам объектами, обеспечивает ряд методов и свойств для работы со ссылочными ключами. В этом процессе есть только одна особенность. Ссылочный ключ это не одно значение, а байтовый массив. Вспомним, байт — целое число в интервале от 0 до 255.
Ниже приведен фрагмент кода, который иллюстрирует получение ссылочных ключей, запись ключа в файл, чтение его из файла, и, наконец, восстановление связи ключа с исходным объектом. Процедура SaveNonBRepKey может быть выполнена для любого документа детали, содержащего хотя бы два эскиза. GetNonBRepKey можно применить для того же самого документа (даже после его закрытия и повторного открытия), чтобы восстановить ключи из файла и использовать их для установления связи с эскизами.
 
<source lang=vbnet>
Public Sub SaveNonBRepKey()
 
Dim oDoc As PartDocument
Set oDoc = ThisApplication.ActiveDocument
' Получение ссылочных ключей (reference keys) для двух эскизов
Dim oSketch As Sketch
Set oSketch = oDoc.ComponentDefinition.Sketches.Item(1)
Dim abtRefKey1() As Byte
Call oSketch.GetReferenceKey(abtRefKey1)
 
Set oSketch = oDoc.ComponentDefinition.Sketches.Item(2)
Dim abtRefKey2() As Byte
Call oSketch.GetReferenceKey(abtRefKey2)
 
' Открываем файл в режиме binary access.
Dim iFile As Integer
iFile = FreeFile
Open "C:\Temp\RefKey.dat" For Binary As #iFile
 
' Запись ссылочных ключей в файл (вместе с длиной массива).
Dim iSize As Long
iSize = UBound(abtRefKey1) - LBound(abtRefKey1)
Put #iFile, , iSize
Put #iFile, , abtRefKey1
iSize = UBound(abtRefKey2) - LBound(abtRefKey2)
Put #iFile, , iSize
Put #iFile, , abtRefKey2
' Закрываем файл.
Close #iFile
End Sub
 
Public Sub GetNonBRepKey()
' Открываем файл.
Dim iFile As Integer
iFile = FreeFile
Open "C:\Temp\RefKey.dat" For Binary As #iFile
' Читаем из файла два ссылочных ключа.
Dim iRefKeyLen As Long
Get #iFile, , iRefKeyLen
Dim abtRefKey1() As Byte
ReDim abtRefKey1(iRefKeyLen) As Byte
Get #iFile, , abtRefKey1
 
Get #iFile, , iRefKeyLen
Dim abtRefKey2() As Byte
ReDim abtRefKey2(iRefKeyLen) As Byte
Get #iFile, , abtRefKey2
 
' Закрываем файл.
Close #iFile
' Получаем ссылку на менеджер ссылочных ключей (reference key manager)
' активного документа.
Dim oRefKeyManager As ReferenceKeyManager
Set oRefKeyManager = ThisApplication.ActiveDocument.ReferenceKeyManager
' Восстановление связи с исходными объектами
On Error Resume Next
Dim oSketch1 As Sketch
Set oSketch1 = oRefKeyManager.BindKeyToObject(abtRefKey1)
If Err Then
MsgBox "Failed to bind back to the first sketch."
Err.Clear
End If
 
Dim oSketch2 As Sketch
Set oSketch2 = oRefKeyManager.BindKeyToObject(abtRefKey2)
If Err Then
MsgBox "Failed to bind back to the second sketch."
Err.Clear
End If
On Error Goto 0
' Используем свойство Name эскиза для проверки работоспособности ссылок.
MsgBox oSketch1.Name
MsgBox oSketch2.Name
End Sub
</source>
 
 
Обратите внимание, попытка восстановить из ключа ссылку вполне может закончиться неудачей. Например, если объект, на который ссылается ключ, уже более не существует. Если в нашем примере вы удалите один из эскизов, вызов метода BindKeyToObject для этого отсутствующего эскиза приведет к ошибке.
 
 
=== Ссылочные ключи для B-Rep объектов ===
 
Использование reference keys в случае объектов B-Rep несколько сложнее. Для объекта B Rep ключ это не просто набор байтов, однозначно идентифицирующих объект, но последовательность байтов, используемая для поиска объекта в таблице. Поскольку объекты B-Rep очень динамичны, определение ссылок на конкретный объект внутри B-Rep оказывается сложным процессом. Полный идентификатор B-Rep в действительности есть описание самого объекта и связанных с ним объектов в составе B-Rep, которое и позволяет Инвентору находить их снова. По этой причине идентификаторы B-Rep могут быть довольно большими. Для минимизации размера ссылочных ключей и с учетом того, что часть этой описательной информации дублируется в других B-Rep объектах, применяются специальные таблицы. На них и ссылаются ссылочные ключи объектов B-Rep. В API эта таблица получила название reference key context — контекст ссылочных ключей.
 
Если понять назначение таблицы контекста, то работать с объектами B-Rep станет не намного сложнее, чем с прочими объектами. Как уже отмечалось, вы всегда можете выбрать ссылочный контекст (reference context), независимо от того, работаете ли вы с объектами B-Rep или нет, обеспечивая внутренние связи в своей программе. Если у вас не B-Rep объект, таблица контекста будет просто проигнорирована. Следующий программный фрагмент иллюстрирует приемы работы со ссылочными ключами для объектов B-Rep.
 
<source lang=vbnet>
Public Sub SaveBRepKey()
Dim oDoc As PartDocument
Set oDoc = ThisApplication.ActiveDocument
' Получение ссылки на выделенную грань
Dim oFace As Face
On Error Resume Next
Set oFace = oDoc.SelectSet.Item(1)
If Err Then
MsgBox "A face must be selected."
Exit Sub
End If
On Error GoTo 0
' Ссылка на ReferenceKeyManager активного документа
Dim oRefKeyManager As ReferenceKeyManager
Set oRefKeyManager = oDoc.ReferenceKeyManager
' Создание контекста ключа.
Dim iKeyContext As Long
iKeyContext = oRefKeyManager.CreateKeyContext
' Создание ключа для грани.
Dim abtRefKey() As Byte
Call oFace.GetReferenceKey(abtRefKey, iKeyContext)
' Открываем файлOpen a file for binary access.
Dim iFile As Integer
iFile = FreeFile
Open "C:\Temp\RefKey.dat" For Binary As #iFile
' Преобразование контекста ключа (key context) в байтовый массив.
Dim abtKeyContext() As Byte
Call oRefKeyManager.SaveContextToArray(iKeyContext, abtKeyContext)
' Запись контекста ключа в файл вместе с длиной массива.
Dim iSize As Long
iSize = UBound(abtKeyContext) - LBound(abtKeyContext)
Put #iFile, , iSize
Put #iFile, , abtKeyContext
' Запись ссылочного ключа в файл вместе с длиной массива.
iSize = UBound(abtRefKey) - LBound(abtRefKey)
Put #iFile, , iSize
Put #iFile, , abtRefKey
' Закрываем файл.
Close #iFile
End Sub
 
Public Sub GetBRepKey()
' Открываем файл.
Dim iFile As Integer
iFile = FreeFile
Open "C:\Temp\RefKey.dat" For Binary As #iFile
' Считываем из файла данные контекста ключа.
Dim iLen As Long
Get #iFile, , iLen
Dim abtKeyContext() As Byte
ReDim abtKeyContext(iLen) As Byte
Get #iFile, , abtKeyContext
' Читаем из файла ключ.
Get #iFile, , iLen
Dim abtRefKey() As Byte
ReDim abtRefKey(iLen) As Byte
Get #iFile, , abtRefKey
' Закрываем файл.
Close #iFile
' Получаем ссылку на менеджер ссылочных ключей активного документа.
Dim oRefKeyManager As ReferenceKeyManager
Set oRefKeyManager = ThisApplication.ActiveDocument.ReferenceKeyManager
' Восстанавливаем контекст ключа (key context).
Dim iKeyContext As Long
iKeyContext = oRefKeyManager.LoadContextFromArray(abtKeyContext)
' Устанавливаем связь с гранью.
' Вернется объект Face (грань), если будет найдена одна грань.
' Если граней будет несколько, то вернется коллекция.
' Если ни одной грани не найдется, будет сгенерирована ошибка.
Dim oResult As Object
On Error Resume Next
Set oResult = oRefKeyManager.BindKeyToObject(abtRefKey, iKeyContext)
If Err Then
MsgBox "The face doesn't exist."
Else
 
' Выделяем полученную грань (грани).
Dim oHS As HighlightSet
Set oHS = ThisApplication.ActiveDocument.HighlightSets.Add
If TypeOf oResult Is Face Then
oHS.AddItem oResult
MsgBox "A single face still exists."
Else
Dim i As Integer
For i = 1 To oResult.Count
oHS.AddItem oResult.Item(i)
Next
MsgBox oResult.Count & " faces now exist for the original face."
End If
oHS.Clear
End If
End Sub
</source>
 
 
Этот пример иллюстрирует две концепции, уникальные для ссылочных ключей B Rep объектов. Первая заключается в использовании контекста ключа (key context). Вы создаете контекст ключа и передаете его идентификатор методам GetReferenceKey и BindKeyToObject. При сохранении данных ключа в файл используется метод SaveContextToArray менеджера контекста, чтобы преобразовать контекст ключа в массив байтов. После считывания из файла информации о контексте ключа вы можете вызвать метод LoadContextFromArray менеджера контекста, чтобы воссоздать контекст ключа из массива байтов.
 
=== Атрибуты (Attributes) ===
 
Атрибуты (attributes) довольно сильно отличаются от ссылочных ключей, но они успешно применяются для решения сходных задач. Вы можете использовать атрибуты для обеспечения доступа к помеченному объекту в любой момент его жизни. Если эта задача является основной, то через атрибуты ее решить проще, чем с помощью ссылочных ключей. Главная причина легкости их применения заключается в том, что вам не нужно беспокоиться о поддержке данных атрибутов, как это обстоит со ссылочными ключами, за вас это сделает Инвентор. Однако для этого вы должны иметь доступ к файлу на запись, т.к. Инвентору необходимо сохранить в файлах данные атрибутов. В большинстве случаев, если вам требуется лишь запомнить конкретный объект для продолжения работы с ним в следующей сессии, атрибутам следует отдать предпочтение.
 
 
Кроме организации ссылок на объекты в последующих сеансах работы Инвентора, атрибуты оказываются полезными и для решения других задач. Функционал атрибутов позволяет «прикрепить» дополнительную информацию практически к любому объекту Инвентора. Позднее эта информация может не только извлекаться из объектов, но и использоваться для организации запросов на поиск конкретных объектов.
 
[[Изображение: Attributes_1.png| Атрибуты (1) ]]
 
На рисунке показана относящаяся к атрибутам часть объектной модели. Здесь Entity — любой объект, поддерживающий атрибуты. Свидетельством этой поддержки является наличие у объекта Entity свойства AttributeSets (указатель на коллекцию ассоциированных с объектом наборов атрибутов).
 
Любой объект может быть связан со многими объектами AttributeSet (набор атрибутов). Наборы атрибутов AttributeSet обязаны иметь уникальные в пределах объекта имена. Множественность наборов атрибутов AttributeSet у любого Entity позволяет всякому приложению формировать собственные и независимые от других приложений наборы атрибутов. Каждый набор AttributeSet может включать любое количество атрибутов — объектов Attribute. Каждый атрибут Attribute имеет имя и значение. Имя атрибута обязано быть уникальным в пределах его набора AttributeSet. Допустимые в атрибутах типы данных: Double, Integer, String и массив байтов (array of Bytes).
 
[[Изображение: Attributes_2.png| Атрибуты (1) ]]
 
После добавления к объекту набора атрибутов AttributeSet и самих атрибутов Attribute, они становятся доступными через сам объект. Однако более общий подход заключается формировании запросов на выборку объектов на базе ассоциированной с ними атрибутной информации. Эту задачу решает менеджер атрибутов AttributeManager, ссылку на который предоставляет объект Document. Менеджер атрибутов AttributeManager может выполнять разнообразные задания на выборку объектов Entity, наборов AttributeSet или самих объектов Attribute.
 
Давайте посмотрим, как можно применить атрибуты для решения задачи из предыдущего примера со ссылочными ключами. Ниже приведен фрагмент, эквивалентный обеим предыдущим процедурам сохранения ключей SaveKey. С точки зрения атрибутов нет различий между B-Rep и не B-Rep объектами.
 
<source lang=vbnet>
Public Sub AddAttribute()
Dim oDoc As PartDocument
Set oDoc = ThisApplication.ActiveDocument
' Get a reference to the selected face.
' получение ссылки на выделенную грань.
Dim oFace As Face
On Error Resume Next
Set oFace = oDoc.SelectSet.Item(1)
If Err Then
MsgBox "Грань должна быть выделена."
Exit Sub
End If
On Error GoTo 0
' Создание набора атрибутов с именем "AttributeSample"
Dim oAttSet As AttributeSet
Set oAttSet = oFace.AttributeSets.Add("AttributeSample")
' Создание атрибута с именем "Face" и текстовым значением "Some data"
Dim oAtt As Inventor.Attribute
Set oAtt = oAttSet.Add("Face", kStringType, "Some data")
End Sub
</source>
 
 
Как и в примере с ключами, процедура сначала определяет ссылку на выделенную пользователем грань oFace, а затем она создает для oFace набор атрибутов и в нем атрибут. Для простой идентификации объекта, как в этом примере, в принципе, достаточно создать лишь пустой набор без каких-либо атрибутов. Собственно, атрибутная информация полезна в случае, когда вы хотите связать с объектом какие-либо дополнительные данные.
 
Пример ниже использует ранее созданный атрибут для поиска ассоциированной с ним грани. Обратите внимание, насколько код выглядит проще и короче, чем в примере со ссылочными ключами.
 
<source lang=vbnet>
Public Sub QueryAttribute()
' Получение ссылки на менеджер атрибутов активного документа
Dim oAttribManager As AttributeManager
Set oAttribManager = ThisApplication.ActiveDocument.AttributeManager
' Запрос на выборку объектов с конкретной атрибутной информацией.
Dim oObjects As ObjectCollection
Set oObjects = oAttribManager.FindObjects("AttributeSample", "Face", "Some data")
' Выделить найденные грани, если таковые будут.
If oObjects.Count > 0 Then
Dim oHS As HighlightSet
Set oHS = ThisApplication.ActiveDocument.HighlightSets.Add
Dim i As Integer
For i = 1 To oObjects.Count
oHS.AddItem oObjects.Item(i)
Next
MsgBox "Found objects are highlighted."
oHS.Clear
End If
End Sub
</source>
 
 
Данный пример вызывает метод FindObjects менеджера атрибутов AttributeManager, чтобы получить коллекцию всех объектов, у которых имеется набор атрибутов AttributeSet с именем «AttributeSample» и в нем атрибут с именем «Face» и текстовым значением «Some data». Все три аргумента — необязательные (optional), и в данном конкретном примере указание любого из них будет достаточным, чтобы получить требуемую грань. К примеру, поставленную задачу успешно выполнит следующая инструкция:
 
<source lang=vbnet>
Set oObjects = oAttribManager.FindObjects("AttributeSample")
</source>
 
Поиск на основе всех трех параметров делает возможным выполнение сложных запросов в документе с множеством разнообразных атрибутов.
 
 
 
== Отмена действий: транзакции ==
 
=== Типичные варианты применения транзакций ===
 
Транзакции (Transaction) используются Инвентором для отслеживания выполняемых действий. С точки зрения пользователя, каждая транзакция есть некоторое действие, которое может быть отменено (Undo). API открывает доступ к транзакциям так, чтобы вы могли управлять из своей программы процессом отмены выполняемых действий. Например, если вы разрабатываете процедуру, которая создает полностью определенный зависимостями эскизный элемент, вы, вероятно, захотите, чтобы конечный пользователь воспринимал эту операцию как неделимое действие, отменяемое за один шаг Undo. Без управления транзакциями через API, пользователь в процессе отмены будет иметь дело с последовательностью элементарных шагов (напр., создана линия, создана еще одна линия, создана дуга, наложена зависимость касательности (tangent constraint), создан управляющий размер (dimension constraint) и т.п.). Без вмешательства в последовательность шагов отмены сама возможность отмены становится для конечного пользователя бесполезной, особенно, если учесть конечный размер списка отмены длиной по умолчанию в 10 действий.
 
 
Функционал транзакций становится доступным через менеджер транзакций (TransactionManager) объекта Application. TransactionManager поддерживает ряд методов, свойств и событий, что дает вам возможность работать с транзакциями. TransactionManager при первом знакомстве слегка озадачивает — довольно много всего, а применение интуитивно непрозрачно. Изначальный вариант транзакционной системы неотчетливо отражает внутреннюю транзакционную функциональность самого Инвентора. На практике же большая часть этого инструментария не используется, поэтому обсуждение будет сфокусировано на наиболее популярных средствах.
[[Изображение: Transactions_1.png| Transactions_1 ]]
 
 
В основе управления транзакциями через API лежит следующая последовательность действий: вы начинаете транзакцию API, вызываете другие функции API, добиваясь желаемого результата, и завершаете транзакцию API. Транзакции, имевшие место между началом и концом транзакции API, скрыты от конечного пользователя. Они еще называются вложенными транзакциями, поскольку ваши промежуточные действия оказываются между началом и завершением транзакции API. Когда конечный пользователь выполняет отмену транзакции API или внешней транзакции, отменяются также и все вложенные в них транзакции. Рассмотрим пример использования транзакций для управления отменой выполненных действий. В примере в активном эскизе создается простой эскизный контур.
 
<source lang=vbnet>
Public Sub DrawKeyway()
' Проверим, активен ли эскиз ?.
If Not TypeOf ThisApplication.ActiveEditObject Is Sketch Then
MsgBox "Эскиз должен быть активным."
Exit Sub
End If
 
Dim oSketch As Sketch
Set oSketch = ThisApplication.ActiveEditObject
' Получим ссылку на объект TransientGeometry, понадобится для задания точек.
Dim oTG As TransientGeometry
Set oTG = ThisApplication.TransientGeometry
' Начало транзакции.
Dim oTrans As Transaction
Set oTrans = ThisApplication.TransactionManager.StartTransaction( _
ThisApplication.ActiveDocument, _
"Keyway Sketch")
' Создание эскизной геометрии.
Dim oLine As SketchLine
Set oLine = oSketch.SketchLines.AddByTwoPoints(oTG.CreatePoint2d(2, 2), _
oTG.CreatePoint2d(2, -2))
Dim oArc As SketchArc
Set oArc = oSketch.SketchArcs.AddByCenterStartEndPoint( _
oTG.CreatePoint2d(0, 0), _
oLine.StartSketchPoint, _
oLine.EndSketchPoint)
' наложение геометрических и размерных зависимостей
Call oSketch.GeometricConstraints.AddVertical(oLine)
Call oSketch.DimensionConstraints.AddDiameter(oArc, oTG.CreatePoint2d(-3, 3))
Call oSketch.DimensionConstraints.AddTwoPointDistance(oLine.StartSketchPoint, _
oArc.CenterSketchPoint, _
kHorizontalDim, _
oTG.CreatePoint2d(0, 3.5))
' Конец транзакции.
oTrans.End
End Sub
</source>
 
 
Этот пример весьма прост, но он демонстрирует основы техники применения API для управления транзакциями. Вызов метода StartTransaction менеджера транзакций TransactionManager создает новую транзакцию. Далее пример вызывает другие функции API, а затем транзакция завершается вызовом метода End объекта Transaction. Обратите внимание на два аргумента в вызове StartTransaction. Первый аргумент вводит в заблуждение многих разработчиков, особенно на начальном этапе знакомства с транзакциями. Первым аргументом является документ. Поскольку при создании транзакции от вас требуется указать документ, вы можете предположить, что транзакции ассоциированы с документом. Это не так.
 
'''Транзакции формируются на уровне приложения и
никак не связаны с какими-либо конкретными документами. '''
 
По этой причине первый аргумент смысла не имеет. Тем не менее, он обязателен, и вам придется его указывать. Достаточно указать любой документ.
 
Вторым аргументом является описание транзакции. Это наименование конечный пользователь увидит в меню Редактирование (Edit), как показано на рисунке.
 
[[Изображение: Transactions_2.png| Transactions_2 ]]
 
 
 
=== Транзакции: вид изнутри ===
 
Прежде, чем далее описывать API для управления транзакциями, позвольте несколько подробнее рассмотреть их работу. Нас будут интересовать нюансы, нередко ускользающие от внимания рядового пользователя.
Когда вы выполняете некоторые действия, в Инвенторе генерируются транзакции. Каждое ваше действие помещается в стек транзакций. Его размер по умолчанию равен десяти транзакциям. Пока транзакция находится в стеке, она может быть отменена (Undone) или выполнена снова (Redone). Рассмотрим примеры.
 
По мере выполнения вами операций транзакции по очереди помещаются в стек. На рисунке ниже показан полностью заполненный стек из десяти транзакций.
 
[[Изображение: Transactions_3.png| Transactions_3 ]]
 
 
Стек сейчас полон, с очередной вашей операцией, которая порождает транзакцию, самая старая транзакция из стека удаляется. Это означает, что отменить ее уже не удастся.
 
[[Изображение: Transactions_4.png| Transactions_4 ]]
 
 
Когда вы выполняете отмену действия (Undo), транзакция остается в стеке, но все, что она делала с моделью, оказывается отмененным.
 
[[Изображение: Transactions_5.png| Transactions_5 ]]
 
 
Отмененные транзакции остаются в стеке, чтобы пользователь смог снова их выполнить — применить к ним операцию «Повторить» (Redo). На следующем рисунке пользователь выполнил подряд три операции отмены — транзакции 9, 10 и 11 отменены. В этот момент все они еще могут быть восстановлены (Redo).
 
Пусть в этот момент пользователь выполнил действие, которое породило очередную транзакцию. Все отмененные на этот момент транзакции из стека удаляются, и туда помещается новая транзакция.
 
[[Изображение: Transactions_6.png| Transactions_6 ]]
 
 
Это означает, что удаленные транзакции более не могут быть восстановлены и выполнены снова. В примере справа выполнение новой транзакции 12 приводит к окончательному удалению из стека транзакций 9, 10 и 11.
 
И последнее, что важно знать и что вызывает наибольшее количество недоразумений, — '''при закрытии документа стек полностью очищается'''. Это означает, что после закрытия документа вы лишаетесь возможности отката любых действий, совершенных к этому моменту. Это распространяется как на видимые (visible), так и на скрытые (hidden) документы. Например, если вы откроете документ, назначив аргументу Visible метода Open значение False, чтобы документ стал невидимым после открытия, выполните какие-нибудь действия в этом документе и закроете его, стек откатов окажется очищенным.
 
 
 
== Структура сборки ==
 
 
Концепция структуры сборки знакома каждому, кто работал со сборками. Инвентор графически отображает эту структуру в браузере. Хотя концептуально организация структуры сборки проста, имеется ряд нюансов, важных при работе со сборкой через API. Наша цель — извлечь пользу из понимания особенностей внутреннего устройства сборок Инвентора. В дальнейшем, в качестве иллюстрации мы будем использовать следующую сборку всего из трех деталей: детали «Ось» и двух экземпляров детали «Колесо».
 
[[Изображение: AssyStructure_1.png| ]]
 
При работе со сборками важно понять, что на самом деле сборка не содержит никакой геометрии — только ссылки на другие файлы. Если бы нам удалось взглянуть изнутри на действительную структуру сборки, мы бы увидели примерно то, что изображено на следующем рисунке. В данном случае сборка содержит две группы данных — ссылки и компоненты. В ссылках определены файлы, на которые ссылается сборка. Ссылке неизвестно, каким образом ее файл используется, ей известно лишь то, что сборка использует этот файл. В данном случае, сборка ссылается на два файла: Axle.ipt и Wheel.ipt.
 
[[Изображение: AssyStructure_2.png| ]]
 
В данных компонентов детализированы все подробности участия компонента в сборке. Неважно, что представляет собой компонент — деталь или подсборку. Здесь хранится ассоциированная с компонентом информация: ссылка на файл компонента, положение и ориентация компонента, имя, цвет, видимость и т.п. Собственно геометрия всегда хранится в деталях (parts), но именно компоненты определяют, каким образом эта геометрия отображается в сборке.
 
API Инвентора предоставляет доступ к информации как ссылок, так и компонентов сборки. Ниже на рисунке показана часть объектной модели сборки (AssemblyDocument) вместе с данными документа сборки. Объект ReferencedFileDescriptor представляет собой конкретную ссылку на файл. Коллекция ReferencedFileDescriptors позволяет организовать цикл по всем ссылкам на файлы сборки. Изменить ссылку возможно через интерфейс Apprentice Server . Объект ComponentOccurrence представляет компоненты сборки, причем как детали, так и подсборки. Коллекция ComponentOccurrences дает возможность организовать цикл по всем компонентам на текущем уровне сборки и, кроме того, поддерживает методы для вставки в этот уровень новых компонентов. Важно помнить, что возвращаемая информация относится к ссылкам и компонентам, непосредственно (напрямую) включенным в сборку, т.е. мы не получим никаких данных о составе подсборок. Получение доступа к содержимому подсборок будет рассмотрено в следующем разделе, посвященном технике обхода всего дерева сборки.
 
[[Изображение: AssyStructure_3.png| ]]
 
 
 
=== Просмотр дерева сборки ===
 
Для многих задач принципиальна возможность просмотра всей иерархии сложной сборки. Рассмотрим пример многоуровневой сборки. Она имеет всего два уровня, но рассматриваемый подход будет работать при любом количестве уровней. В данном примере сборка верхнего уровня Car.iam состоит из двух ранее рассмотренных колесных сборок и кузовной детали. Схема слева отображает внутреннее представление сборки Car.iam. Обратите внимание, она содержит информацию только об элементах на своем верхнем уровне. Нет никаких данных о составе колесных подсборок. Их состав определен в подсборке WheelAssembly.iam.
[[Изображение: AssyStructure_4.png| ]]
 
Рассмотрим фрагмент API, который обеспечивает просмотр как текущего, так и всех подчиненных уровней сборки, и получение всей необходимой информации о составляющих ее деталях и подсборках. Дальнейшее рассмотрение будет опираться на приведенный справа фрагмент объектной модели документа сборки.
 
Для просмотра дерева сборки нам сначала необходимо у объекта AssemblyComponentDefinition получить ссылку на коллекцию ComponentOccurrences компонентов данной сборки. Итеративный перебор элементов этой коллекции возвращает объекты типа ComponentOccurrence. Если компонент представляет собой деталь, вы можете исследовать ее геометрию, опираясь на возвращаемую компонентом информацию о поверхностях. Если же компонент оказался подсборкой, свойство SubOccurrences объекта ComponentOccurrence предоставит возможность перебора составляющих ее компонентов. Свойство SubOccurrences возвращает объект ComponentOccurrenceEnumerator, весьма похожий на коллекцию ComponentOccurrences. Отличие состоит в том, что он не поддерживает добавление новых компонентов и служит исключительно целям перебора уже имеющихся в подсборке компонентов. При обходе дерева сборки произвольной вложенности вы просто опускаетесь на уровень ниже, если объект ComponentOccurrence оказывается подсборкой.
 
[[Изображение: AssyStructure_5.png| ]]
 
<source lang=vbnet>
Public Sub AssemblyTraversal()
' Ссылка на активный документ. Полагаем, что это сборка.
Dim oAsmDoc As AssemblyDocument
Set oAsmDoc = ThisApplication.ActiveDocument
' Начинаем обход сборки
Call TraverseAsm(oAsmDoc.ComponentDefinition.Occurrences, 1)
End Sub
 
' Аргумент Level необходим для вычисления левого отступа при печати.
Private Sub TraverseAsm(oOccurrences As ComponentOccurrences, Level As Integer)
' перебор списка компонентов на текущем уровне иерархии.
Dim oOcc As ComponentOccurrence
For Each oOcc In oOccurrences
' вывод на печать имени текущего компонента
Debug.Print Space(Level * 3) & oOcc.Name
' Если текущий компонент – подсборка, то вызываем эту процедуру
' снова с текущим компонентом в качестве параметра.
If oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then
Call TraverseAsm(oOcc.SubOccurrences, Level + 1)
End If
Next
End Sub
</source>
 
 
Первое, что бросается в глаза — задача решается не одной процедурой, а двумя. Для просмотра сборочной иерархии произвольной вложенности мы вынуждены воспользоваться рекурсивной процедурой. Здесь рекурсия — вызов функции или процедуры из неё же самой. Процедура TraverseAsm выполняет всю работу по перебору компонентов на уровне текущей сборки и, если наткнется на подсборку, вызывает сама себя для ее просмотра. Так продолжается до тех пор, пока не будут пройдены все подсборки. Процедура же AssemblyTraversal лишь дает старт этому процессу.
Результат данного примера очень прост — будут напечатаны имена всех компонентов сборки по мере их обнаружения. Однако с каждым компонентом может быть выполнена гораздо более сложная операция. Например, у деталей могут быть вычислены объемы, которые, будучи просуммированы, дадут общий объем всей сборки. Другой пример — формирование спецификации, в процессе чего потребуется и доступ к документу, на который ссылается компонент, и точная информация о свойствах.
 
Со структурой сборок тесно связана концепция прокси-объектов (proxy).
 
 
 
== Прокси-объекты (Proxy) ==
 
 
=== Что такое прокси-объект ? ===
 
Как уже отмечалось, сборки содержат только ссылки, но не геометрию. На первый взгляд, это противоречит опыту конечных пользователей, которые в Инвенторе создают и редактируют сборки. С их точки зрения геометрия деталей реально участвует в сборках. Например, при наложении зависимости совмещения двух деталей вы вольны выбрать грань детали, не задумываясь, где в действительности находится информация о геометрии этой детали. Примерно так же обстоит дело и при работе с API — вы можете просматривать иерархию сборки, получая доступ к ее компонентам и их содержимому, как если бы они находились на верхнем уровне сборки.
 
В предыдущем примере с демонстрацией перебора компонентов сборки мы так и делали. Мы могли просматривать все ветви сборки и получать ссылки на любой объект ComponentOccurrence в пределах сборки. Если мы попробуем выяснить положение объекта ComponentOccurrence, ответом будет положение, как если бы он находился на верхнем уровне сборки. Если компонент ссылается на деталь, и мы с его помощью получим ссылку на объект B-Rep, информация о геометрии B-Rep будет возвращаться в контексте верхнего уровня сборки.
 
Чтобы увидеть, как это работает, вернемся к сборке WheelAssembly. В ней имеются два экземпляра детали Колесо (Wheel). Каждый экземпляр (instance) представлен компонентом ComponentOccurrence. Допустим, мы хотим, чтобы пользователь выделил внешние цилиндрические поверхности каждого колеса. Первый вопрос: каким образом пользователь сможет это сделать, если геометрия деталей не присутствует в сборке? Второй вопрос: если предположить, что мы каким-то образом можем выделить геометрию, как отличить внешний цилиндр одного колеса от внешнего цилиндра другого, если в действительности в детали Wheel существует лишь один внешний цилиндр?
 
[[Изображение: Proxy_1.jpg| ]]
 
Ответом на оба вопроса будет — прокси-объекты (proxy). Прокси-объекты представляют объекты в сборке так, как если бы эти объекты на самом деле существовали в сборке. К идее прокси-объектов надо привыкнуть, но как только их концепция станет понятной, работать с ними будет уже нетрудно. Вернемся к цилиндрическим граням колес. Когда вы выделяете в сборке одну из них, вы в действительности выделяете объект FaceProxy. Объект FaceProxy наследуется из объекта Face, поэтому он имеет те же методы и свойства, плюс еще несколько, характерных только для прокси-объектов. С объектом FaceProxy, как наследником объекта Face, вы как правило можете работать как с обычной гранью, и даже не требуется особо задумываться, что в действительности вы работаете с прокси-объектом. Для работы с объектом вы используете те же методы и свойства, отличие лишь в том, что получаемые результаты будут определяться положением объекта в сборке.
 
Прокси-объект устроен довольно просто. Это путь к объекту. Например, пути к цилиндрическим граням двух наших деталей-колес будут иметь следующий вид:
 
Wheel:1\CylinderFace
Wheel:2\CylinderFace
 
 
Здесь «CylinderFace» представляет реальную цилиндрическую грань детали колеса.
 
Первая часть пути — компонент, последняя часть пути — действительный объект. В многоуровневой сборке путь будет длиннее, потому что в нем будет много компонентов. В сборке всей машины с четырьмя колесами путь к цилиндрической грани одного из колес имеет вид:
 
WheelAssembly:1\Wheel:2\CylinderFace
 
Благодаря пути прокси-объекта, Инвентор в состоянии точно определить, на какой именно объект в сборке вы ссылаетесь.
 
Прокси-объекты поддерживаются для большого количества объектов. Эта поддержка необходима всякому объекту, который в сборке должен быть выделяемым. На самом деле нам уже приходилось иметь дело с прокси-объектами ComponentOccurrenceProxy — в программе перебора компонентов сборки, но это происходило неявно, поскольку мы могли обрабатывать их как обычные компоненты ComponentOccurrence. Компоненты на верхнем уровне сборки являются обычными объектами ComponentOccurrence. Они на самом деле существуют на верхнем уровне сборки. Когда вы перебираете компоненты в любой из подсборок WheelAssembly, возвращаемые объекты имеют тип ComponentOccurrenceProxy. Это так, потому что эти компоненты входят в сборку не на верхнем уровне, а принадлежат колесным подсборкам. Объект ComponentOccurrenceProxy создает видимость принадлежности компонента верхнему уровню сборки. Если запросить у ComponentOccurrenceProxy его положение, он вернет его в контексте верхнего уровня сборки.
 
Обычно, при получении прокси-объекта в результате перебора сборки или выбора пользователем, вас нисколько не волнует, что вы работаете с прокси-объектом, а не с самим объектом. Именно в этом и состоит самая замечательная особенность прокси-объектов — они упрощают работу с объектами из различных файлов, обеспечивая видимость, будто все объекты существуют непосредственно в самой сборке, как это и представляется конечному пользователю.
 
 
=== Создание прокси-объектов ===
 
Бывают случаи, когда необходимо создать прокси-объект для передачи его другим методам в качестве аргумента. Чаще всего это случается при программном создании сборки и наложении сборочных зависимостей. Рассмотрим конкретный пример. Пусть имеется болт, который мы желаем автоматически вставлять в сборку. Круглое ребро, указанное на рисунке справа, будет использоваться как аргумент при наложении зависимости. Чтобы легко найти это ребро, когда болт будет уже в сборке, к ребру добавлен атрибут «AutoBolts».
 
[[Изображение: Proxy_2.jpg| ]]
 
 
Рассмотрим внимательнее, что происходит в сборке, изображенной на рисунке справа. Сборка состоит из двух деталей, между которыми мы намереваемся установить зависимость. Предположим, мы уже получили компонент-кубик и, используя механику B-Rep объектов, — круглое ребро отверстия. Поскольку мы добрались до ребра через компонент, это будет прокси-объект EdgeProxy, что означает, что он ведет себя как если бы это ребро действительно существовало в сборке. Теперь нам следует получить объект EdgeProxy для ребра в болте. Для поиска ребра к нему был добавлен атрибут, однако этот атрибут существует в детали Болт, а не в сборке. Если попробовать искать атрибут в сборке, мы его не найдем. На самом деле искать атрибут следует в пределах документа Болт. Поскольку. мы проводим поиск в документе Болт, возвращаемый объект будет действительным ребром, а не его прокси-объектом. API предоставляет нам инструменты для создания прокси-объекта. Пример иллюстрирует доступ к документу детали, запрос на поиск ребра, создание прокси-объекта и, наконец, наложение сборочной зависимости. Предполагается, что у мы уже имеем EdgeProxy для ребра на кубике и ссылку на компонент Болт.
 
[[Изображение: Proxy_3.jpg| ]]
 
<source lang=vbnet>
' Доступ к определению компонента Болт из самого компонента Болт.
Dim oBoltCompDef As ComponentDefinition
Set oBoltCompDef = oBoltOccurrence.Definition
 
‘ Запрос на поиск ребра посредством менеджера атрибутов документа Болт
Dim oAttribManager As AttributeManager
Set oAttribManager = oBoltCompDef.Document.AttributeManager
Dim oObjects As ObjectCollection
Set oObjects = oAttribManager.FindObjects("AutoBolts")
 
Dim oBoltEdge As Edge
Set oBoltEdge = oObjects.Item(1)
 
' Создание для ребра прокси-объекта.
Dim oAsmBoltEdge As EdgeProxy
Call oBoltOccurrence.CreateGeometryProxy(oBoltEdge, oAsmBoltEdge)
 
' Наложение сборочной зависимости.
Call oDoc.ComponentDefinition.Constraints. _
AddInsertConstraint(oBlockEdge, _
oAsmBoltEdge, _
True, 0)
</source>
 
 
Пример иллюстрирует ряд важных моментов. В свойстве Definition компонент ComponentOccurrence возвращает объект ComponentDefinition документа, на который компонент ссылается. В нашем случае результатом будет объект PartComponentDefinition детали Болта. Это тот же самый объект ComponentDefinition, который мы получаем как свойство ComponentDefinition документа PartDocument. Всякий раз, когда вы используете свойство Definition компонента, вы покидаете контекст сборки и далее все операции будут выполняться в контексте детали. А имея доступ к документу детали, вы получаете в свое распоряжение весь предназначенный для этих объектов инструментарий API. К примеру, вам доступны на изменение значения параметров, и можно даже добавлять новые конструктивные элементы.
Через свойство Document объекта ComponentDefinition можно получить ссылку на родительский документ определения компонента. В данном случае будет получена ссылка на объект PartDocument болта. В примере этот документ используется для получения доступа к менеджеру атрибутов документа Болт. Он вызывает метод FindObjects менеджера атрибутов для получения ссылки на ассоциированное с атрибутом ребро.
 
Как только ребро в детали найдено, следует создать прокси-объект для представления этого ребра в сборке. Как уже отмечалось, прокси-объект определяет путь к объекту. В файле детали мы имеем объект, и теперь нам требуется добавить к нему путь, чтобы определить тот же объект, но уже в сборке. Делается это с помощью метода CreateGeometryProxy объекта ComponentOccurrence. На вход метода подается объект, на выходе имеем соответствующий прокси-объект. Метод всего-навсего добавляет компонент к пути прокси-объекта. В итоге мы имеем объект, представляющий ребро в контексте сборки.
 
Последняя строка примера использует прокси-объект болта и прокси-объект кубика для наложения зависимости вставки.
 
На вход методу CreateGeometryProxy можно подать и прокси-объект. Именно так формируется многоуровневая сборка. Чтобы ее получить, вы начинаете снизу от самого объекта и затем вызываете метод CreateGeometryProxy на каждом уровне сборки, пока не выстроите полный прокси-путь.
 
 
=== Работа с прокси-объектами ===
 
API содержит также ряд дополнительных инструментов для работы с прокси-объектами. Многие из них полезны не только в обычной работе, но и для первоначального знакомства с прокси-объектами и для отладки программ с прокси-объектами.
 
Как уже отмечалось, различные прокси-объекты наследуются из соответствующих им действительных объектов. По этой причине они поддерживают все методы и свойства объектов-родителей. Но есть у них два дополнительных свойства, которых у родителей нет — ContainingOccurrence и NativeObject. Свойство ContainingOccurrence возвращает компонент ComponentOccurrence, через который виден прокси-объект. В предыдущем примере свойство ContainingOccurrence прокси-объекта ребра в болте вернет компонент Болт.
 
Свойство NativeObject возвращает настоящий объект, представляемый прокси-объектом. В нашем примере, свойство NativeObject объекта EdgeProxy вернет дествительный объект Ребро (Edge).
 
Одним из общих свойств объектов и их прокси является свойство Parent (родитель). Но хотя свойство общее, возвращаемая ссылка указывает на разных родителей. В нашем примере, свойство Parent настоящей грани вернет объект SurfaceBody детали, тогда как Parent прокси-объекта EdgeProxy вернет объект SurfaceBodyProxy, представляющий тело в контексте сборки.
Еще две дополнительных функции — свойство OccurrencePath и метод AdjustProxyContext. Свойство OccurrencePath, поддерживаемое компонентом ComponentOccurrence и его прокси ComponentOccurrenceProxy, возвращает путь к компоненту. Это тот же путь, который использует любой прокси-объект данного компонента.
 
Метод AdjustProxyContext дает возможность обрезать (trim) путь прокси-объекта. Например, если вы имеете 4-уровневую сборку и прокси-объект грани в контексте сборки, вы можете методом AdjustProxyContext сократить его путь так, чтобы прокси-объект был представлен в контексте одной из промежуточных подсборок.