Пользовательский интерфейс через API Fusion 360

Автор: | 04.02.2018

Модификация пользовательского интерфейса
Создание пользовательских диалогов для команд

Модификация пользовательского интерфейса

При настройке пользовательского интерфейса Fusion необходимо выделить два понятия – модификация пользовательского интерфейса и создание пользовательских диалогов для команд.

На рисунке выделены 3 разновидности пользовательского интерфейса:

  • toolbars – красным цветом;
  • toolbar panels – зеленым цветом;
  • work space – синим цветом.

Каждая из них описана ниже более подробно.

Toolbars представляет собой простой контейнер для элементов управления. Элементы управления могут быть простыми командными кнопками или выпадающим списком, который содержит несколько элементов управления. Есть три toolbars, которые всегда отображаются и содержимое которых остаются неизменными независимо от того, что происходит в Fusion. Есть также toolbars, которые наиболее часто редактируются, как правило, путем добавления команд. Каждая из этих toolbars имеет уникальный идентификатор, который может использоваться для доступа к ним.

Toolbar в левом верхнем углу экрана обычно известен как панели быстрого доступа (QAT or Quick Access Toolbar). Его идентификатор QAT. Он обеспечивает доступ ко всем командам по работе с  файлом.

Toolbar в правом верхнем углу экрана обеспечивает доступ к учетной записи пользователя и Help командам, его идентификатор «QATRight».

Toolbar в нижней части центра окна называется панелью навигации и имеет все команды, связанные с видом, его идентификатор «NavToolbar»

Все toolbars доступны из объекта UserInterface. Вы можете использовать свойство itemById этого объекта, чтобы получить определенный toolbar. Пример кода Python, который открывает доступ к toolbar с идентификатором QAT.

app = adsk.core.Application.get()
ui = app.userInterface
qatToolbar = ui.toolbars.itemById('QAT')

Toolbar panels также имеют идентификатор, который обеспечивает доступ к элементам управления. Toolbar panels обеспечивают доступ к более широкому набору команд в более организованный способ, чем toolbar,  причем команды могут динамически изменяться в зависимости от текущего активного рабочего пространства. Если активным есть рабочее пространство MODEL, то Toolbar panels содержат команды, необходимые для работы в этом пространстве. Каждая Toolbar panel состоит из двух частей – панель и выпадающий список.

Пользователь может контролировать, какие команды из выпадающего списка отображаются на панели.

Есть два способа доступа к toolbar panels через API – непосредственно из UserInterfaceObject, и от объекта Workspace. В первом случае Вы получаете все toolbar panels, во втором случае получаете только подмножество toolbar panels, связанных с активным рабочим пространством.

Если вы запрашиваете все toolbar panels, Вы увидите, что свойство IsVisible многих из них будет False, поскольку они в настоящее время не видны в Fusion. Когда пользователь изменяет активное рабочее пространство, Fusion изменяет видимость toolbar panels и включает только те, которые относятся к активному рабочему пространству.

В API есть только один тип объекта CommandDefinition, который связан с различными типами элементов  интерфейса.  Объекты  ControlDefinition, ButtonControlDefinition, CheckBoxControlDefinition или объект ListControlDefinition определяются через  объект CommandDefinition. Это представлено в диаграмме объектной модели.

Ниже приведен Python код, который демонстрирует использование некоторых из этих объектов путем создания кнопки и добавления ее в нижнюю часть ADD-INS панели в рабочем пространстве MODEL. Также создается событие кнопки, чтобы получить уведомление, когда кнопка нажата.

# Get the UserInterface object and the CommandDefinitions collection.
ui = app.userInterface
cmdDefs = ui.commandDefinitions
# Create a button command definition.
buttonExample = cmdDefs.addButtonDefinition('MyButtonDefId', 'Sample Button', 'Sample button tooltip','.//Resources//Sample')
# Connect to the command created event.
buttonExampleCreated = ButtonExampleCreatedEventHandler()
buttonExample.commandCreated.add(buttonExampleCreated)
handlers.append(buttonExampleCreated)
# Get the ADD-INS panel in the model workspace.
addInsPanel = ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel')
# Add the button to the bottom.
buttonControl = addInsPanel.controls.addCommand(buttonExample)
# Make the button available in the panel.
buttonControl.isPromotedByDefault = True
buttonControl.isPromoted = True

Ниже приведен Python код, который демонстрирует удаление определения команды и элемента управления, которые были созданы выше.

# Get the UserInterface object and the CommandDefinitions collection.
ui = app.userInterface
cmdDefs = ui.commandDefinitions
# Delete the button definition.
buttonExample = ui.commandDefinitions.itemById('MyButtonDefId')
if buttonExample:
buttonExample.deleteMe()
# Get panel the control is in.
addInsPanel = ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel')
# Get and delete the button control.
buttonControl = addInsPanel.controls.itemById('MyButtonDefId')
if buttonControl:
buttonControl.deleteMe()

Что необходимо знать, чтобы расположить элемент управления в нужном месте? Например, необходимо вставить новую команду TestCommand One в раскрывающемся меню панели QAT над командой 3D Print.

Для того, чтобы создать элемент управления в этом месте, мы должны знать имя панели инструментов, имя раскрывающегося меню и имя команды 3D Print. Вот код, который выполняет это:

# Get the QAT toolbar.
qat = ui.toolbars.itemById('QAT')
# Get the drop-down that contains the file related commands.
fileDropDown = qat.controls.itemById('FileSubMenuCommand')
# Get the control for the 3D Print command.
print3DCmd = fileDropDown.controls.itemById('ThreeDprintCmdDef')
# Add a new button next to the 3D Print control.
buttonControl = fileDropDown.controls.addCommand(testCommandDef, 'testCmdDefControl', print3DCmd.index)

В результате получаем древовидную структуру пользовательского интерфейса. Ниже приводится урезанная версия файла, который показывает элементы интерфейсной части. Из него мы видим, что имя панели быстрого доступа является «QAT», название меню файла есть «FileSubMenuCommand«, и название команды 3D Print есть «ThreeDprintCmdDef«.

** Toolbars (7) **
.
.
.
QAT (10)____
.
.
Drop Down Control (27)____
ID: FileSubMenuCommand
.
.
.
Command Control____
Control ID: ExportCommand
isVisible: True
index: 12
Command Control____
Control ID: ThreeDprintCmdDef
isVisible: True
index: 13
Command Control____
Control ID: SaveAsImageCommand
isVisible: True
index: 14

Создание пользовательских диалогов для команд

Fusion имеет четко определенную концепцию о том, что такое команда.  На высоком уровне команда – это последовательный процесс подготовки ожидаемого результата. Пользователь нажимает кнопку для запуска команды, через диалог реализуется процесс сбора необходимых данных, затем обеспечивается предварительный просмотр ожидаемого результата, и, наконец, создается конечный результат, который может быть отменен.

Как скрипты, так и add-ins приложения могут создавать команды, но чаще всего они создаются add-ins приложениями. Причина, по которой для создания команд используются add-ins чаще, потому, что add-ins могут быть автоматически загружены при запуске Fusion и как часть процесса загрузки они могут добавить кнопки для команд в пользовательском интерфейсе Fusion. При этом пользователь может выполнить пользовательскую команду, нажав на кнопку, так же, как это делается для любой другой команды Fusion. Скрипты же извлекаются только через кнопку Scripts and Add-Ins и это не совсем удобно.

Определение команды в add-ins приложениях в  первую очередь описывает, как команда будет выглядеть в пользовательском интерфейсе. Например, для отображения кнопки в пользовательском интерфейсе нужно описать ее расположение, значок, подсказку, включенное состояние, видимость и т.д. В дополнение к кнопке, есть и другие типы определений команд, которые вы видите в пользовательском интерфейсе – списки check box или radio buttons, текстовые окна.

При запуске  команды Fusion создает новый объект Command и запускает событие commandCreated, а также передает объект Command для add-in приложения. Оно реагирует на это событие путем подключения к другим событиям, связанных с командой, и диалога команды, если он есть.

Ниже приведен простейший Python код базового add-in приложения для команды, которая не имеет диалога. Приложение создает командную кнопку, а затем ждет события, которое уведомляет её, когда кнопка была нажата пользователем, а затем сразу реагирует на это. В результате этой команды просто отображается окно сообщения.

import adsk.core, adsk.fusion, adsk.cam, traceback
# Global list to keep all event handlers in scope.
# This is only needed with Python.
handlers = []
def run(context):
ui = None
try:
app = adsk.core.Application.get()
ui  = app.userInterface
# Get the CommandDefinitions collection.
cmdDefs = ui.commandDefinitions
# Create a button command definition.
buttonSample = cmdDefs.addButtonDefinition('MyButtonDefIdPython',
'Python Sample Button',
'Sample button tooltip',
'./Resources/Sample')
# Connect to the command created event.
sampleCommandCreated = SampleCommandCreatedEventHandler()
buttonSample.commandCreated.add(sampleCommandCreated)
handlers.append(sampleCommandCreated)
# Get the ADD-INS panel in the model workspace.
addInsPanel = ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel')
# Add the button to the bottom of the panel.
buttonControl = addInsPanel.controls.addCommand(buttonSample)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
# Event handler for the commandCreated event.class SampleCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)
cmd = eventArgs.command
# Connect to the execute event.
onExecute = SampleCommandExecuteHandler()
cmd.execute.add(onExecute)
handlers.append(onExecute)
# Event handler for the execute event.
class SampleCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandEventArgs.cast(args)
# Code to react to the event.
app = adsk.core.Application.get()
ui  = app.userInterface
ui.messageBox('In command execute event handler.')
def stop(context):
try:
app = adsk.core.Application.get()
ui  = app.userInterface
# Clean up the UI.
cmdDef = ui.commandDefinitions.itemById('MyButtonDefIdPython')
if cmdDef:
cmdDef.deleteMe()
addinsPanel = ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel')
cntrl = addinsPanel.controls.itemById('MyButtonDefIdPython')
if cntrl:
cntrl.deleteMe()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

Все, что вы делаете в execute event handler, поставляется в рамках одной транзакции и может быть отменено одним Undo. Без этого, каждый API вызов будет отображаться как отдельная операция в списке отмен.

Например, если вы пишете простой скрипт, который рисует три линии, чтобы создать треугольник в эскизе, нужно будет выполнить команду Undo три раза, чтобы отменить изменения. Если же вызывается тот же код из execute event handler, один Undo отменит все изменения.

Ниже приведен простой пример, который демонстрирует, как это делается в скрипте. Большая часть кода аналогична add-in коду, за исключением кода создания пользовательского интерфейса. Кроме этого, в коде выделены 2 особенности. Во-первых, вызывается метод execute для выполнения команды, которую создали. Во-вторых, command definition устанавливает свойство stop, чтобы остановить скрипт автоматически. По умолчанию скрипт Python автоматически прекращается только после того, как функция запуска закончена.

import adsk.core, adsk.fusion, adsk.cam, traceback
# Global list to keep all event handlers in scope.
# This is only needed with Python.
handlers = []
def run(context):
ui = None
try:
app = adsk.core.Application.get()
ui  = app.userInterface
# Get the CommandDefinitions collection.
cmdDefs = ui.commandDefinitions
# Create a button command definition.
buttonSample = cmdDefs.addButtonDefinition('SampleScriptButtonId',
'Python Sample Button',
'Sample button tooltip')
# Connect to the command created event.
sampleCommandCreated = SampleCommandCreatedEventHandler()
buttonSample.commandCreated.add(sampleCommandCreated)
handlers.append(sampleCommandCreated)
# Execute the command.
buttonSample.execute()
# Keep the script running.
adsk.autoTerminate(False)
except:
if ui:     ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
# Event handler for the commandCreated event.
class SampleCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)
cmd = eventArgs.command
# Connect to the execute event.
onExecute = SampleCommandExecuteHandler()
cmd.execute.add(onExecute)
handlers.append(onExecute)
# Event handler for the execute event.
class SampleCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandEventArgs.cast(args)
# Code to react to the event.
app = adsk.core.Application.get()
des = adsk.fusion.Design.cast(app.activeProduct)
if des:
root = des.rootComponent
sk = root.sketches.add(root.xYConstructionPlane)
lines = sk.sketchCurves.sketchLines
l1 = lines.addByTwoPoints(adsk.core.Point3D.create(0,0,0),
adsk.core.Point3D.create(5,0,0))
l2 = lines.addByTwoPoints(l1.endSketchPoint,
adsk.core.Point3D.create(2.5,4,0))
l3 = lines.addByTwoPoints(l2.endSketchPoint, l1.startSketchPoint)
# Force the termination of the command.
adsk.terminate()   
def stop(context):
try:
app = adsk.core.Application.get()
ui  = app.userInterface
# Delete the command definition.
cmdDef = ui.commandDefinitions.itemById('SampleScriptButtonId')
if cmdDef:
cmdDef.deleteMe()
except:
if ui:     ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

В предыдущих примерах ничего не было сделано в commandCreated event кроме подключения execute event. Ниже приводится новая версия SampleCommandCreatedEventHandler для  примера add-in приложения, рассмотренного выше. Handler по-прежнему подключается к execute event, но он также создает три команды inputs, которые отображаются в диалоговом окне.

import adsk.core, adsk.fusion, adsk.cam, traceback
# Event handler for the commandCreated event.
class SampleCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)
# Get the command
cmd = eventArgs.command
# Get the CommandInputs collection to create new command inputs.
inputs = cmd.commandInputs
# Create a check box to get if it should be an equilateral triangle or not.
equilateral = inputs.addBoolValueInput('equilateral', 'Equilateral',True, '', False)
# Create the slider to get the base length, setting the range of the slider to
# be 1 to 10 of whatever the current document unit is.
app = adsk.core.Application.get()
des = adsk.fusion.Design.cast(app.activeProduct)
minVal = des.unitsManager.convert(1, des.unitsManager.defaultLengthUnits, 'cm' )
maxVal = des.unitsManager.convert(10, des.unitsManager.defaultLengthUnits, 'cm' )
baseLength = inputs.addFloatSliderCommandInput('baseLength','Base Length',
des.unitsManager.defaultLengthUnits,minVal, maxVal, False)
# Create the value input to get the height scale.
heightScale = inputs.addValueInput('heightScale', 'Height Scale',
'', adsk.core.ValueInput.createByReal(0.75))
# Connect to the execute event.
onExecute = SampleCommandExecuteHandler()
cmd.execute.add(onExecute)
handlers.append(onExecute)

Выделенный ниже код может быть добавлен к commandCreated event, чтобы убедиться, что эскиз в настоящее время активен и прервать команду, если это не так.

import adsk.core, adsk.fusion, adsk.cam, traceback
def notify(self, args):
# Verify that a sketch is active.
app = adsk.core.Application.get()
if app.activeEditObject.objectType != adsk.fusion.Sketch.classType():
ui = app.userInterface
ui.messageBox('A sketch must be active for this command.')
return False
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)

Ниже приведен код для execute event для этого примера, где он получает значения из command inputs в диалоговом окне, а затем вызывает функцию, которая рисует равнобедренный треугольник в активном эскизе.

# Event handler for the execute event.
class SampleCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
import math
eventArgs = adsk.core.CommandEventArgs.cast(args)
# Get the values from the command inputs.
inputs = eventArgs.command.commandInputs
length = inputs.itemById('baseLength').valueOne
if inputs.itemById('equilateral').value == True:
# Specify a scale that will result in an equilateral triangle.
scale = math.sqrt(length**2 - (length/2)**2) / length
else:
# Get the scale from the command input.
scale = inputs.itemById('heightScale').value
drawTriangle(length, scale)
def drawTriangle(baseLength, heightScale):
# Get the active sketch.
app = adsk.core.Application.get()
sketch = adsk.fusion.Sketch.cast(app.activeEditObject)
sketch.isComputeDeferred = True
if sketch:
# Draw the three lines for the triangle.
lines = sketch.sketchCurves.sketchLines
l1 = lines.addByTwoPoints(adsk.core.Point3D.create(0,0,0),
adsk.core.Point3D.create(baseLength,0,0))
l2 = lines.addByTwoPoints(l1.endSketchPoint,
adsk.core.Point3D.create(baseLength/2, baseLength*heightScale, 0))
l3 = lines.addByTwoPoints(l2.endSketchPoint, l1.startSketchPoint)
return True
else:
return False
sketch.isComputeDeferred = False

Выше был продемонстрирован основной рабочий процесс для команды, которая использует конкретный диалог. Тем не менее, есть много других более простых возможностей. Например, Вы можете выбрать интерактивно, какие из событий вы хотите реализовать в зависимости от потребностей команды.

Автор: Николай Свирневский

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *