Extending DAVE#
When adding (proprietary) elements to DAVE you may want to extend the GUI without changing the source-code of DAVE itself.
This section describes how to integrate
workspaces
dockwidgets
nodes
into the GUI.
Note
The DAVE gui is open-source. If the functionality is insufficient for your purpose then you may implement this yourself and be added to the DAVE Gui via a PR on github.
New Node types#
Registering#
Registering the class#
DAVE executes python code when importing files, copying the scene or adding nodes.
New classes need to be made available to the environment when running code in order to be usable.
This is done by adding them to a global setting DAVE_ADDITIONAL_RUNTIME_MODULES
:
# Add the balloon class to the modules that are available when executing code
from DAVE.settings import DAVE_ADDITIONAL_RUNTIME_MODULES
DAVE_ADDITIONAL_RUNTIME_MODULES['Balloon'] = Balloon
Registering the properties and documentation#
The properties of the and their documentation need to be added to DAVE as well. This makes them available for timelines, limits derived-properties, reporting and others.
The properties are registered in the following nested dictionary:
from DAVE.settings import DAVE_NODEPROP_INFO,NodePropertyInfo
# type is the node-type, for example Point
# property-name is the name of the property, for example 'global_position'
info = DAVE_NODEPROP_INFO[type][property_name]
# info is a NodePropertyInfo object
The properties of nodes are used in various places in the guis and reporting:
The limits screen to define limits for single numerical properties
Properties with a single numerical value
The derived properties screen - to show the values of derived properties
Anything interesting, typically numerical and boolean values and sequences of those
type = float, bool
Reporting module
All properties that have a physical meaning and/or define how the model is structured (parent, meshes, etc)
Timeline module
All settable single properties, including nodes and booleans
type = float/bool/node
settable = True
Exploration module
All settable single numerical properties
Besides that we need to have the following documentation available for each node/property combination:
short description (1 line)
long description
unit, eg [m]
remarks, eg (global) or (parent)
Here the property may be inherited and/or overridden.
settable: not read-only
single-settable: can be used in time-line - bool, Node,
single-numeric: can be used as limit
Class |
Property |
Short doc |
Long doc |
Unit |
Remarks |
type |
settable |
single-settable |
single-numeric |
---|---|---|---|---|---|---|---|---|---|
Point |
x |
x-position |
x-position… |
[m] |
(parent) |
float |
X |
X |
X |
Point |
parent |
parent |
parent… |
(Node) |
Frame |
X |
X |
||
Node |
name |
name |
name… |
(unique) |
str |
X |
|||
Point |
position |
position |
position… |
[m,m,m] |
(parent) |
float |
X |
||
Point |
applied_force |
force |
force … |
[kN,kN,kN] |
(parent) |
float |
X |
||
Point |
force |
|force| |
force … |
[kN] |
float |
X |
|||
Frame |
fixed_x |
bool |
X |
X |
|||||
Frame |
fixed |
bool |
X |
||||||
Node |
visible |
visible in GUI |
bool |
X |
X |
||||
Cable |
connections |
Point|Circle |
X |
How, where and when#
When looking up the properties this will be done using a Node object and optionally a property name (these two are therefore the index of the database). Matching class needs to be done on an is-instance basis and considering overriding.
Say:
class A has property p
class B derives from A and overrides property p
class C derives from B
How
requesting the documentation for property p on a node of class C should give the documentation of p of class B. This can be done using the mro. The property of the class with the lowest index in the mro of the node class is the one we want.
The data is stored as a nested dictionary:
data[class][propname(str)] = node_property_info
with node_property_info being a NodePropertyInfo
dataclass
There is a convenience function to register individual properties:
from DAVE.settings import register_nodeprop
register_nodeprop(HEBO_CraneVessel,'hoist_length_main_sb',
property_type=float,
doc_short="SB main hoist length",
doc_long="SB main hoist length",
units = "m",
remarks="",
is_settable=True,
is_single_numeric=True,
is_single_settable=True)
But if the docstrings and type-hints are correct, then the code to do this can be automatically generated via DAVE.helpers.generate_node_documentation.generate_python_code
from DAVE import Point
from DAVE.helpers.generate_node_documentation import generate_python_code
code = generate_python_code(Point) #<-- automatically geneartes the code
filename = r"C:\data\demo.py"
with open(filename,'w') as f:
f.write('\n'.join(code))
Defining the graphics for a new node#
The new node needs to be painted, labeled and shown in the node tree.
Both the paint and the icon of a node are defined in settings_visual
. The icons are defined in a dict
ICONS[type(node)] = QIcon(...)
The paint is defined in the PAINTERS
dictionary. This dictionary is organized by class-name (str). A convenience function AddPaintForNodeClassAsCopyOfOtherClass
is available to quickly define a copy of an existing paint.
Icons can only be created when an QApplication is present, so one needs to be created before defining the icon.
However, Gui
will loop through the ICONS dictionary in the constructor and will replace and string values entries with QIcon(that string). So setting the icon as a string (as below) also works and is preferred:
from DAVE.settings_visuals import AddPaintForNodeClassAsCopyOfOtherClass, ICONS
# Define paint
AddPaintForNodeClassAsCopyOfOtherClass('Balloon','Frame')
# Icons
# if QApplication.instance() is None:
# app = QApplication()
#
# ICONS[Balloon] = QIcon(':/icons/Balloon.png')
ICONS[Balloon] = ':/icons/Balloon.png'
Creating a new dock#
Docks are floating or docked sub-windows of the main GUI. Everything except the 3d view-port and the menu is a dock.
Docks are QtWidgets derived from guiDockWidget
Docks are created and activated by main. Upon creation they receive some references to things in the gui:
d.guiScene = self.scene
d.guiEmitEvent = self.guiEmitEvent
d.guiRunCodeCallback = self.run_code
d.guiSelectNode = self.guiSelectNode
d.guiSelection = self.selected_nodes
d.guiPressSolveButton = self.solve_statics
d.gui = self
Communication between the docks is done via the guiEmitEvent
which can send any of the ENUMs defined in guiEventType
. Handling of these events is to be implemented in guiProcessEvent.
Registering#
Any dock needs to be added to the DAVE_GUI_DOCKS
dictionary. The keys in this dictionary are strings. These strings are also used as the dock-window title.
from DAVE.gui.dockwidget import DAVE_GUI_DOCKS
DAVE_GUI_DOCKS['Balloon'] = BalloonDock
Activating a dock#
See workspaces
Integrating into the main gui#
Code can be executed at various moment by defining plugin-functions and adding a reference to that function to a globally available list.
Init#
plugin_init is executed at the end of the .init function.
It takes a single parameter being the instance of the Gui that is being intialized (
the self
of the init function)
def my_function(gui):
print('executed on startup')
from DAVE.gui.main import DAVE_GUI_PLUGINS_INIT
DAVE_GUI_PLUGINS_INIT.append(my_function)
Editing nodes#
Selecting a node while in “construction” makes the “Properties” dock-widget visible.
Extending the node-editor#
Sections in the node-propeties widget derive from NodeEditor
Create a new
MyNewNodeEditor
class which derives fromNodeEditor
Register the node-editor class against the node-instances for which the editor should open.
from DAVE.gui.widget_nodeprops import DAVE_GUI_NODE_EDITORS
DAVE_GUI_NODE_EDITORS[Balloon] = BalloonNodeEditor
# Note: Balloon is the Node-Class, BalloonNodeEditor is the editor class
Alternatively#
Create a new dock that reacts to the “SELECTION_CHANGED” event.