├── README.md ├── __init__.py ├── bin ├── SceneGraph ├── SceneGraph.bat ├── build_plugin └── sgpm ├── cfg ├── default-fonts.cfg ├── default-palette.cfg ├── nuke-fonts.cfg └── nuke-palette.cfg ├── core ├── __init__.py ├── attributes.py ├── events.py ├── graph.py ├── logger.py ├── metadata.py ├── nodes.py └── plugins.py ├── data ├── __init__.py ├── globals.ini ├── main_menu.json └── scene_tab_menu.json ├── doc ├── Makefile ├── SceneGraph_setup.dia ├── examples │ └── big_graph.json ├── images │ ├── EdgeWidget.png │ ├── NodeWidget.png │ ├── add_attribute_dialog.png │ ├── add_node_menu.png │ ├── attr_editor_dot.png │ ├── autosave_time.png │ ├── drag_edge01.png │ ├── edge_type.png │ ├── expand_node_check.png │ ├── goals_note.png │ ├── intro.png │ ├── intro_debug.png │ ├── layouts_add.png │ ├── layouts_restore.png │ ├── node_expand_animated.gif │ ├── output_terminal.png │ ├── plugins_manager.png │ ├── plugins_menu.png │ ├── prefs_pane.png │ ├── render_fx.png │ └── src │ │ ├── default_colors.xcf │ │ ├── drag_edge01.xcf │ │ ├── edge_type.xcf │ │ ├── intro.xcf │ │ ├── node_expand01.xcf │ │ ├── node_expand_animated.xcf │ │ └── render_fx.xcf ├── make.bat └── source │ ├── api.rst │ ├── api │ ├── core.rst │ ├── grid.py │ ├── ui.rst │ └── util.rst │ ├── conf.py │ ├── extending.rst │ ├── index.rst │ ├── overview.rst │ └── tutorial.rst ├── icn ├── __init__.py ├── graph_icon.png ├── icons.py ├── scenegraph.qrc ├── scenegraph_rc.py └── src │ ├── arrows-flat.xcf │ ├── checks.xcf │ ├── graph_icon.xcf │ ├── maya_2016_style-tabs.png │ ├── maya_2016_style.png │ ├── node_base_250x180.svg │ ├── sizegrip.xcf │ ├── spin-arrows-flat.xcf │ ├── ui-dock-buttons-assets │ └── ui-dock-buttons.png │ ├── ui-dock-buttons-converted.xcf │ ├── ui-dock-buttons.psd │ └── ui-dock-buttons.xcf ├── mtd ├── dagnode.mtd ├── default.mtd ├── dot.mtd └── note.mtd ├── options.py ├── plugins ├── README ├── __init__.py ├── asset.mtd ├── asset.py ├── asset_widget.py ├── lookdev.mtd ├── lookdev.py ├── lookdev_widget.py ├── merge.mtd ├── merge.py ├── merge_widget.py ├── model.mtd ├── model.py ├── model_widget.py ├── texture.mtd ├── texture.py └── texture_widget.py ├── qss ├── nuke.qss └── stylesheet.qss ├── scenegraph.py ├── scenegraph_maya.py ├── scenegraph_nuke.py ├── test ├── TestGraph.ui ├── __init__.py ├── nodes.py └── test.py ├── ui ├── AttributeManager.py ├── AttributeManager.ui ├── GraphAttributes.py ├── PaletteEditor.ui ├── PluginManager.py ├── PluginManager.ui ├── SceneGraph.ui ├── __init__.py ├── attributes.py ├── commands.py ├── designer │ ├── AddAttributeDialog.ui │ ├── AttributeEditor.ui │ ├── Console.ui │ ├── GraphAttributes.ui │ ├── Preferences.ui │ └── widgets │ │ ├── DockWidget_stylesheet.ui │ │ ├── FileEditor.ui │ │ └── Slider.ui ├── graphics.py ├── handlers.py ├── models.py ├── node_widgets.py ├── settings.py └── stylesheet.py └── util └── __init__.py /README.md: -------------------------------------------------------------------------------- 1 | # SceneGraph 2 | 3 | ![Alt text](/doc/images/intro.png?raw=true "SceneGraph") 4 | 5 | **SceneGraph** is a fast & flexible framework for visualizing node graphs in visual effects CC applications using PySide. Scenes can be saved and loaded in a variety of environments, and users can easily extend the toolset to include their own node types. 6 | 7 | ## Requirements 8 | 9 | * [PySide][pyside-url] 10 | * [NetworkX 1.9.1][networkx-url] 11 | * [simplejson][simplejson-url] 12 | 13 | 14 | ## Installation 15 | 16 | Clone the repository and ensure that the directory is included on your `PYTHONPATH`: 17 | 18 | ```bash 19 | git clone git@github.com:mfessenden/SceneGraph.git 20 | ``` 21 | 22 | For Linux/macOS, symlink the launcher `bin/SceneGraph` somewhere on your `PATH`. For Windows, symlink `bin/SceneGraph.bat`. 23 | 24 | ## Usage 25 | 26 | To launch the UI via the command line (Linux/macOS), simply call: 27 | 28 | ```bash 29 | $ SceneGraph 30 | ``` 31 | 32 | (For Windows, double-click the `SceneGraph.bat` shortcut). 33 | 34 | 35 | 36 | ## SceneGraph API 37 | 38 | ```python 39 | from SceneGraph import scenegraph 40 | 41 | # show the UI 42 | sgui = scenegraph.SceneGraphUI() 43 | sgui.show() 44 | 45 | # access the node graph instance 46 | graph = sgui.graph 47 | 48 | # list all node names 49 | node_names = graph.node_names() 50 | 51 | # query all of the node widgets from the `GraphicsScene` 52 | scene = sgui.view.scene() 53 | nodes = scene.nodes() 54 | 55 | # query all of the current DAG nodes 56 | dags = graph.nodes() 57 | 58 | # return a named DAG node 59 | dagnode = graph.get_node('node1') 60 | 61 | # query node attributes 62 | dagnode.getNodeAttributes() 63 | 64 | # set arbitrary attributes 65 | dagnode.setNodeAttributes(env='maya', version='2014') 66 | 67 | # querying widgets 68 | 69 | # get an output connection widget 70 | c_output=n1.get_connection('output') 71 | 72 | # query an edge 73 | e1=scene.get_edge('node1.output', 'node2.input')[0] 74 | 75 | # get edge source item (Connection) 76 | e1.source_item 77 | 78 | # get connected nodes from an edge 79 | e1.listConnections() 80 | 81 | ``` 82 | 83 | ### Maya Usage 84 | 85 | ```python 86 | 87 | from SceneGraph import scenegraph_maya 88 | scenegraph_maya.main() 89 | 90 | ``` 91 | 92 | ### Nuke Usage 93 | 94 | ```python 95 | 96 | from SceneGraph import scenegraph_nuke 97 | scenegraph_nuke.main() 98 | 99 | ``` 100 | 101 | ## API 102 | 103 | ```python 104 | # create a graph 105 | from SceneGraph import core 106 | g = core.Graph() 107 | 108 | # query the currently loaded node types 109 | node_types = g.node_types() 110 | 111 | # add some default nodes 112 | n1 = g.add_node('default', name='node1') 113 | n2 = g.add_node('default', name='node2') 114 | 115 | # query node connections 116 | if n1.is_input_connection: 117 | conn = n1.output_connections() 118 | 119 | # add a new input and output attribute 120 | n1.add_input(name='fileIn') 121 | 122 | # connect the nodes (default output/inputs assumed) 123 | e1 = g.add_edge(n1, n2) 124 | 125 | # connect two nodes via a connection string 126 | e1 = g.connect('node1.output', 'node2.input') 127 | 128 | # write the current graph to disk 129 | g.write('~/graphs/my_graph.json') 130 | 131 | # dump all nodes in the graph 132 | print g.nodes() 133 | 134 | # dump all node names 135 | print g.node_names() 136 | 137 | # dump all connections 138 | print g.connections() 139 | 140 | # updating attributes 141 | d1 = g.add_node('default') 142 | m1 = g.add_node('merge') 143 | 144 | # connect two nodes via a named attribute 145 | g.add_edge(d1, m1, dest_attr='inputA') 146 | 147 | # change the input name 148 | m1.rename_connection('inputA', 'newInput') 149 | 150 | # add attributes to a dag node & flag it as an input connection 151 | attr = n1.addAttr('env', value='maya', input=True) 152 | 153 | # set an attribute value via the node 154 | n1.env = 'nuke' 155 | 156 | # set an attribute value via the attribute instance 157 | attr.value = 'houdini' 158 | ``` 159 | 160 | 161 | [pyside-url]:https://pypi.org/project/PySide/ 162 | [simplejson-url]:https://simplejson.readthedocs.io/en/latest/ 163 | [networkx-url]:https://networkx.org 164 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __package__ = 'SceneGraph' 3 | __version__ = None 4 | __version_info__ = () 5 | 6 | 7 | def main(): 8 | global __version__ 9 | global __version_info__ 10 | from . import options 11 | print '[%s]: INFO: initializing %s %s...' % (options.PACKAGE, options.PACKAGE, options.API_VERSION_AS_STRING) 12 | __version__ = options.API_VERSION_AS_STRING 13 | __version_info__ = [int(x) for x in str(options.API_MAJOR_VERSION).split('.')] 14 | __version_info__.extend([options.API_REVISION, "development", 0]) 15 | __version_info__ = tuple(__version_info__) 16 | 17 | 18 | main() -------------------------------------------------------------------------------- /bin/SceneGraph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from PySide import QtGui, QtCore 5 | from optparse import OptionParser 6 | 7 | 8 | def _path_fix(): 9 | """ 10 | Append path for windows. 11 | """ 12 | dname = os.path.dirname(__file__) 13 | rootdir = os.path.dirname(os.path.dirname(dname)) 14 | sys.path.append(rootdir) 15 | 16 | 17 | if __name__ == "__main__": 18 | _path_fix() 19 | from SceneGraph import scenegraph 20 | from SceneGraph.options import PACKAGE, PLATFORM, EDGE_TYPES, VIEWPORT_MODES 21 | 22 | APP_STYLES = [s.lower() for s in QtGui.QStyleFactory.keys()] 23 | 24 | filename = None 25 | args = sys.argv 26 | if len(args)>1: 27 | if os.path.exists(args[1]): 28 | filename = args[1] 29 | 30 | parser = OptionParser() 31 | parser.add_option("-g", "--use_gl", action="store_true", dest="use_gl", default=False, help='use OpenGL renderer.') 32 | parser.add_option("--fx", action="store_true", dest="render_fx", default=True, help='render node dropshadow & glow effects.') 33 | parser.add_option("-m", action="store", dest="viewport_mode", help='set the Qt viewport mode (ie. "full", "smart", "minimal").') 34 | parser.add_option("-e", action="store", dest="edge_type", help='node edge style (ie. "bezier", "polygon").') 35 | parser.add_option("-s", action="store", dest="app_style", help='Qt application style (ie. "%s").' % '", "'.join(APP_STYLES)) 36 | parser.add_option("--style", action="store_false", dest="use_stylesheet", default=True, help='ignore builtin stylsheet.') 37 | 38 | # oxygen looks better in linux overall, but plastique groupBoxes look better 39 | # Designer previews in GTK+ 40 | # Maya style is close to plastique 41 | if PLATFORM == 'Linux': 42 | parser.set_default('app_style', 'plastique') 43 | 44 | if PLATFORM == 'MacOSX': 45 | parser.set_default('app_style', 'cleanlooks') 46 | 47 | if PLATFORM == 'Windows': 48 | parser.set_default('app_style', 'plastique') 49 | 50 | (options, args) = parser.parse_args() 51 | 52 | app = QtGui.QApplication(sys.argv) 53 | 54 | # set the application style 55 | if options.app_style: 56 | if options.app_style.lower() in APP_STYLES: 57 | #print '[%s]: INFO: setting Qt style: "%s".' % (PACKAGE, options.app_style) 58 | app.setStyle(options.app_style) 59 | 60 | # update prefs 61 | prefs = dict() 62 | if options.viewport_mode: 63 | if options.viewport_mode.lower() in VIEWPORT_MODES: 64 | prefs.update(viewport_mode=options.viewport_mode.lower()) 65 | 66 | if options.edge_type: 67 | if options.edge_type.lower() in EDGE_TYPES: 68 | prefs.update(edge_type=options.edge_type.lower()) 69 | 70 | sgui = scenegraph.SceneGraphUI(use_gl=options.use_gl, render_fx=options.render_fx, use_stylesheet=options.use_stylesheet, **prefs) 71 | sgui.show() 72 | 73 | if filename: 74 | sgui.readGraph(filename) 75 | sys.exit(app.exec_()) 76 | -------------------------------------------------------------------------------- /bin/SceneGraph.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL ENABLEEXTENSIONS 3 | SET me=%~n0 4 | SET parent=%~dp0 5 | echo %parent% 6 | SET myScript=%parent%/SceneGraph 7 | python %myScript% %* 8 | :: pause for debugging 9 | :: pause 10 | -------------------------------------------------------------------------------- /bin/build_plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/bin/build_plugin -------------------------------------------------------------------------------- /bin/sgpm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from PySide import QtGui, QtCore 5 | from optparse import OptionParser 6 | 7 | 8 | if __name__ == "__main__": 9 | from SceneGraph.ui import PluginManager 10 | app = QtGui.QApplication(sys.argv) 11 | pmui = PluginManager.PluginManager() 12 | pmui.show() 13 | sys.exit(app.exec_()) 14 | -------------------------------------------------------------------------------- /cfg/default-fonts.cfg: -------------------------------------------------------------------------------- 1 | @font-family-ui:Linux = DejaVu Sans 2 | @font-family-ui:MacOSX = Verdana 3 | @font-family-ui:Windows = Lucida Sans Unicode 4 | @font-family-mono:Linux = DejaVu Sans Mono 5 | @font-family-mono:MacOSX = Menlo 6 | @font-family-mono:Windows = Lucida Console 7 | @font-family-nodes:Linux = DejaVu Sans 8 | @font-family-nodes:MacOSX = Menlo 9 | @font-family-nodes:Windows = Lucida Console 10 | @font-size-ui:Linux = 8pt 11 | @font-size-ui:MacOSX = 12pt 12 | @font-size-ui:Windows = 8pt 13 | @font-size-mono:Linux = 10pt 14 | @font-size-mono:MacOSX = 14pt 15 | @font-size-mono:Windows = 9pt 16 | @font-size-ui:header:Linux = 0 17 | @font-size-ui:header:MacOSX = 0 18 | @font-size-ui:header:Windows = 0 19 | @font-size-mono:status:Linux = -2 20 | @font-size-mono:status:MacOSX = -2 21 | @font-size-mono:status:Windows = -1 22 | @font-size-mono:console:Linux = -1 23 | @font-size-mono:console:MacOSX = -2 24 | @font-size-mono:console:Windows = -1 -------------------------------------------------------------------------------- /cfg/default-palette.cfg: -------------------------------------------------------------------------------- 1 | @background-color = #444444 2 | @background-color-darker = #3e3e3e 3 | 4 | @widget-color = #d7d7d7 5 | @widget-color-hover = #ff53c8 6 | @widget-color-selected = #eeeeee 7 | 8 | @widget-bg-color = #2b2b2b 9 | @widget-bg-hover = #5285a6 10 | @widget-bg-selected = #5285a6 11 | 12 | @mid-color = #646464 13 | @mid-darker = #5A5A5A 14 | @mid-darkest = #404040 15 | 16 | @border-groove = #1C1C1C 17 | @border-dark = #292929 18 | @border-swatch = #2a2a2a 19 | 20 | @status-border = #383838 21 | @status-color = #c4c4c4 22 | @status-bg-color = #3e3e3e 23 | 24 | @menu-bg-color = #525252 25 | @menu-border-color = #424242 26 | @menu-separator-color = #6f6f6f 27 | 28 | @tab-subtab-bg = #658269 29 | @tab-title-color-selected = #e3e3e3 30 | @tab-title-color-deselected = #e3e3e3 31 | @tab-top-selected = #6d6d6d 32 | @tab-top-hover = #818181 33 | @tab-top-deselected = #363636 34 | 35 | @header-bg-color = #2b2b2b 36 | @header-border = #4a4a4a 37 | 38 | @button-bg-med = #5e5e5e 39 | @button-bg-color = #5d5d5d 40 | @button-bg-selected = #6e6e6e 41 | @button-bg-hover = #707070 42 | @button-bg-disabled = #515151 43 | @button-color-disabled = #7b7b7b 44 | 45 | @button-border-light = #7c7c7c 46 | @button-border-mid = #454545 47 | @button-border = #626262 48 | @button-border-selected = #696969 49 | @button-border-hover = #767676 50 | @output-bg-color = #222222 51 | 52 | 53 | @dock-title-bg-color = #5d5d5d 54 | @dock-title-border-color = #404040 55 | 56 | @group-border-color = #353535 57 | @group-flat-border-color = #444444 58 | 59 | @combo-bg-color1 = #5d5d5d 60 | @combo-bg-color2 = #5d5d5d 61 | @combo-border-color = #303030 62 | @combo-border-color-left = #515151 63 | @combo-border-left = #515151 64 | @combo-dropdown-bg-color = #5d5d5d 65 | @combo-dropdown-bg-border = #4f4f4f 66 | 67 | @view-border-dark = #303030 68 | 69 | @line-border-light = #373737 70 | @line-bg-dark = #2b2b2b 71 | @line-bg-dark-disabled = #404040 72 | @line-bg-connected = #f1f1a5 73 | @line-color-connected = #000000 74 | 75 | @pink = #ff53c8 76 | @purple = #9769dc -------------------------------------------------------------------------------- /cfg/nuke-fonts.cfg: -------------------------------------------------------------------------------- 1 | @font-family-ui:Linux = DejaVu Sans 2 | @font-family-ui:MacOSX = Verdana 3 | @font-family-ui:Windows = Lucida Sans Unicode 4 | @font-family-mono:Linux = DejaVu Sans Mono 5 | @font-family-mono:MacOSX = Menlo 6 | @font-family-mono:Windows = Lucida Console 7 | @font-family-nodes:Linux = DejaVu Sans 8 | @font-family-nodes:MacOSX = Menlo 9 | @font-family-nodes:Windows = Lucida Console 10 | @font-size-ui:Linux = 8pt 11 | @font-size-ui:MacOSX = 12pt 12 | @font-size-ui:Windows = 8pt 13 | @font-size-mono:Linux = 10pt 14 | @font-size-mono:MacOSX = 14pt 15 | @font-size-mono:Windows = 9pt 16 | @font-size-ui:header:Linux = 0 17 | @font-size-ui:header:MacOSX = 0 18 | @font-size-ui:header:Windows = 0 19 | @font-size-mono:status:Linux = -2 20 | @font-size-mono:status:MacOSX = -2 21 | @font-size-mono:status:Windows = -1 22 | @font-size-mono:console:Linux = -1 23 | @font-size-mono:console:MacOSX = -2 24 | @font-size-mono:console:Windows = -1 -------------------------------------------------------------------------------- /cfg/nuke-palette.cfg: -------------------------------------------------------------------------------- 1 | @background-color = #323232 2 | @background-color-darker = #313131 3 | 4 | @widget-color = #d7d7d7 5 | @widget-color-hover = #ffa02f 6 | @widget-color-selected = #eeeeee 7 | 8 | @widget-bg-color = #2b2b2b 9 | @widget-bg-hover = #ffa02f 10 | @widget-bg-selected = #ffa02f 11 | 12 | @mid-color = #646464 13 | @mid-darker = #5A5A5A 14 | @mid-darkest = #404040 15 | 16 | @border-groove = #1C1C1C 17 | @border-dark = #292929 18 | @border-swatch = #2a2a2a 19 | 20 | @status-border = #383838 21 | @status-color = #c4c4c4 22 | @status-bg-color = #323232 23 | 24 | @menu-bg-color = #525252 25 | @menu-border-color = #424242 26 | @menu-separator-color = #6f6f6f 27 | 28 | @tab-subtab-bg = #313131 29 | @tab-title-color-selected = #ffa02f 30 | @tab-title-color-deselected = #d1d1d1 31 | @tab-top-selected = #323232 32 | @tab-top-hover = #353535 33 | @tab-top-deselected = #262626 34 | @tab-border-color = #171717 35 | 36 | @header-bg-color = #2b2b2b 37 | @header-border = #4a4a4a 38 | 39 | @button-bg-med = #5e5e5e 40 | @button-bg-color = #5d5d5d 41 | @button-bg-selected = #6e6e6e 42 | @button-bg-hover = #707070 43 | @button-bg-disabled = #515151 44 | @button-color-disabled = #7b7b7b 45 | 46 | @button-border-light = #7c7c7c 47 | @button-border-mid = #454545 48 | @button-border = #626262 49 | @button-border-selected = #696969 50 | @button-border-hover = #767676 51 | @output-bg-color = #222222 52 | 53 | 54 | @dock-title-bg-color = #5d5d5d 55 | @dock-title-border-color = #404040 56 | 57 | @group-border-color = #353535 58 | @group-flat-border-color = #323232 59 | 60 | @combo-bg-color1 = #5d5d5d 61 | @combo-bg-color2 = #5d5d5d 62 | @combo-border-color = #303030 63 | @combo-border-color-left = #515151 64 | @combo-border-left = #515151 65 | @combo-dropdown-bg-color = #5d5d5d 66 | @combo-dropdown-bg-border = #4f4f4f 67 | 68 | @view-border-dark = #303030 69 | 70 | @line-border-light = #373737 71 | @line-bg-dark = #2b2b2b 72 | @line-bg-dark-disabled = #404040 73 | @line-bg-connected = #f1f1a5 74 | @line-color-connected = #000000 75 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from . import logger 3 | log = logger.myLogger() 4 | 5 | from . import attributes 6 | # attributes 7 | Attribute = attributes.Attribute 8 | 9 | 10 | from . import events 11 | # events 12 | EventHandler = events.EventHandler 13 | 14 | 15 | from . import metadata 16 | # Parsers/Managers 17 | MetadataParser = metadata.MetadataParser 18 | 19 | 20 | # Plugin Manager 21 | from . import plugins 22 | PluginManager = plugins.PluginManager 23 | 24 | 25 | from . import graph 26 | # graph class 27 | Graph = graph.Graph 28 | -------------------------------------------------------------------------------- /core/attributes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import simplejson as json 3 | import weakref 4 | from collections import OrderedDict as dict 5 | from SceneGraph import util 6 | 7 | 8 | class Attribute(object): 9 | """ 10 | Generic Attribute class. 11 | """ 12 | attribute_type = 'generic' 13 | REQUIRED = ['name', 'attr_type', 'value', '_edges'] 14 | 15 | def __init__(self, name, value, dagnode=None, user=True, **kwargs): 16 | 17 | # private attributes 18 | self._dag = weakref.ref(dagnode) if dagnode else None 19 | 20 | # stash argument passed to 'type' - overrides 21 | # auto-type mechanism. * this will become data_type 22 | self._type = kwargs.get('attr_type', None) 23 | self._edges = [] 24 | 25 | self.name = name 26 | self.label = kwargs.get('label', "") 27 | self.default_value = kwargs.get('default_value', "") 28 | self.value = value 29 | 30 | self.doctstring = kwargs.get('doctstring', '') 31 | self.desc = kwargs.get('desc', '') 32 | 33 | # globals 34 | self.user = user 35 | self.private = kwargs.get('private', False) # hidden 36 | self.hidden = kwargs.get('hidden', False) 37 | self.connectable = kwargs.get('connectable', False) 38 | self.locked = kwargs.get('locked', False) 39 | self.required = kwargs.get('required', False) 40 | 41 | # connection 42 | self.connection_type = kwargs.get('connection_type', 'input') 43 | self.data_type = kwargs.get('data_type', None) 44 | self.max_connections = kwargs.get('max_connections', 1) # 0 = infinite 45 | 46 | if self.connectable: 47 | #print 'Connection "%s" attr_type: %s' % (self.name, self._type) 48 | #print 'Connection "%s" data_type: %s' % (self.name, self.data_type) 49 | pass 50 | 51 | def __str__(self): 52 | return json.dumps({self.name:self.data}, indent=4) 53 | 54 | def __repr__(self): 55 | return json.dumps({self.name:self.data}, indent=4) 56 | 57 | def update(self, **kwargs): 58 | """ 59 | Update attributes. 60 | 61 | .. todo:: 62 | - can't pass as **kwargs else we lose the order (why is that?) 63 | """ 64 | for name, value in kwargs.iteritems(): 65 | if value not in [None, 'null']: 66 | # we don't save edge attributes, so don't read them from disk. 67 | if name not in ['_edges']: 68 | #print '# adding attribute: "%s"' % name 69 | if hasattr(self, name) and value != getattr(self, name): 70 | print '# DEBUG: Attribute "%s" updating value: "%s": "%s" - "%s"' % (self.name, name, value, getattr(self, name)) 71 | setattr(self, name, value) 72 | 73 | @property 74 | def data(self): 75 | """ 76 | Output data for writing. 77 | 78 | :returns: attribute data. 79 | :rtype: dict 80 | """ 81 | data = dict() 82 | #for attr in self.REQUIRED: 83 | for attr in ['label', 'value', 'desc', '_edges', 'attr_type', 'private', 84 | 'hidden', 'connectable', 'connection_type', 'locked', 'required', 'user']: 85 | if hasattr(self, attr): 86 | value = getattr(self, attr) 87 | if value or attr in self.REQUIRED: 88 | #if value or attr in self.REQUIRED: 89 | data[attr] = value 90 | return data 91 | 92 | @property 93 | def dagnode(self): 94 | """ 95 | :returns: dag node parent. 96 | :rtype: DagNode 97 | """ 98 | return self._dag() 99 | 100 | @property 101 | def attr_type(self): 102 | if self._type is not None: 103 | return self._type 104 | return util.attr_type(self.value) 105 | 106 | @attr_type.setter 107 | def attr_type(self, val): 108 | self._type = val 109 | 110 | @property 111 | def is_input(self): 112 | """ 113 | :returns: attribute is an input connection. 114 | :rtype: bool 115 | """ 116 | if not self.connectable: 117 | return False 118 | return self.connection_type == 'input' 119 | 120 | @property 121 | def is_output(self): 122 | """ 123 | :returns: attribute is an output connection. 124 | :rtype: bool 125 | """ 126 | if not self.connectable: 127 | return False 128 | return self.connection_type == 'output' 129 | 130 | def rename(self, name): 131 | """ 132 | Rename the attribute. 133 | 134 | :param str name: new name. 135 | """ 136 | old_name = self.name 137 | self.name = name 138 | 139 | -------------------------------------------------------------------------------- /core/events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | class EventHandler(object): 5 | 6 | def __init__(self, sender): 7 | 8 | self.callbacks = [] 9 | self.sender = sender 10 | self.blocked = False 11 | 12 | def __call__(self, *args, **kwargs): 13 | """ 14 | Runs all callbacks. 15 | """ 16 | if not self.blocked: 17 | return [callback(self.sender, *args, **kwargs) for callback in self.callbacks] 18 | return [] 19 | 20 | def __iadd__(self, callback): 21 | """ 22 | Add a callback to the stack. 23 | """ 24 | self.add(callback) 25 | return self 26 | 27 | def __isub__(self, callback): 28 | """ 29 | Remove a callback to the stack. 30 | 31 | :param callable callback: callback function/method. 32 | """ 33 | self.remove(callback) 34 | return self 35 | 36 | def __len__(self): 37 | return len(self.callbacks) 38 | 39 | def __getitem__(self, index): 40 | return self.callbacks[index] 41 | 42 | def __setitem__(self, index, value): 43 | self.callbacks[index] = value 44 | 45 | def __delitem__(self, index): 46 | del self.callbacks[index] 47 | 48 | def blockSignals(self, block): 49 | """ 50 | Temporarily block the handler from signalling its observers. 51 | 52 | :param bool block: block signals. 53 | """ 54 | self.blocked = block 55 | 56 | def add(self, callback): 57 | """ 58 | Add a callback. Raises error if callback is not 59 | callable. 60 | 61 | :param callable callback: callback function or method. 62 | """ 63 | if not callable(callback): 64 | raise TypeError("callback must be callable") 65 | self.callbacks.append(callback) 66 | 67 | def remove(self, callback): 68 | """ 69 | Remove a callback. 70 | 71 | :param callable callback: callback function or method. 72 | """ 73 | self.callbacks.remove(callback) 74 | 75 | -------------------------------------------------------------------------------- /core/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | import sys 4 | import os 5 | import datetime 6 | 7 | 8 | LOGGERS = {} 9 | LOGGER_LEVEL = logging.INFO 10 | 11 | 12 | def myLogger(name=None): 13 | global LOGGERS 14 | from SceneGraph import options 15 | 16 | if name is None: 17 | name = options.PACKAGE 18 | 19 | if LOGGERS.get(name): 20 | return LOGGERS.get(name) 21 | else: 22 | logger=logging.getLogger(name) 23 | logger.setLevel(LOGGER_LEVEL) 24 | 25 | console_handler = logging.StreamHandler() 26 | file_handler = logging.FileHandler(getLogFile(name)) 27 | 28 | formatter_console = logging.Formatter('[%(name)s]: %(levelname)s: %(message)s') 29 | formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 30 | 31 | # set handler formatters 32 | console_handler.setFormatter(formatter_console) 33 | file_handler.setFormatter(formatter_file) 34 | file_handler.setLevel(logging.WARNING) 35 | 36 | # add handlers 37 | logger.addHandler(console_handler) 38 | logger.addHandler(file_handler) 39 | 40 | logger.propagate = False 41 | LOGGERS[options.PACKAGE]=logger 42 | return logger 43 | 44 | 45 | def enableDebugging(): 46 | """ Enables debugging on the logger """ 47 | global LOGGER_INITIALIZED 48 | global LOGGER_LEVEL 49 | LOGGER_INITIALIZED = False 50 | LOGGER_LEVEL = logging.DEBUG 51 | return 52 | 53 | 54 | def disableDebugging(): 55 | """ Disables debugging on the logger """ 56 | global LOGGER_INITIALIZED 57 | global LOGGER_LEVEL 58 | LOGGER_INITIALIZED = False 59 | LOGGER_LEVEL = logging.INFO 60 | return 61 | 62 | 63 | def getLogFile(name): 64 | """ Returns the user log file """ 65 | import os 66 | from SceneGraph import options 67 | if not os.path.exists(options.SCENEGRAPH_PREFS_PATH): 68 | os.makedirs(options.SCENEGRAPH_PREFS_PATH) 69 | return os.path.join(options.SCENEGRAPH_PREFS_PATH, '%s.log' % name) 70 | -------------------------------------------------------------------------------- /core/metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from copy import deepcopy 4 | from collections import OrderedDict as dict 5 | import simplejson as json 6 | import re 7 | 8 | from SceneGraph.core import log 9 | 10 | 11 | regex = dict( 12 | section = re.compile(r"^\[[^\]\r\n]+]"), 13 | section_value = re.compile(r"\[(?P[\w]*?) (?P[\w\s]*?)\]$"), 14 | properties = re.compile("(?P[\.\w]*)\s*(?P\w*)\s*(?P.*)$"), 15 | ) 16 | 17 | 18 | PROPERTIES = dict( 19 | min = 'minimum value', 20 | max = 'maximum value', 21 | default = 'default value', 22 | label = 'node label', 23 | private = 'attribute is private (hidden)', 24 | desc = 'attribute description,' 25 | ) 26 | 27 | 28 | class MetadataParser(object): 29 | """ 30 | class MetadataParser: 31 | 32 | DESCRIPTION: 33 | read and parse node metadata (.mtd) files to build a 34 | template for the AttributeEditor widget. 35 | 36 | Metadata is stored as: 37 | 38 | [section $name] 39 | 40 | [attr $attr_name] 41 | $property_name $property_type $property_value 42 | 43 | """ 44 | def __init__(self, filename=None, **kwargs): 45 | 46 | self._template = filename 47 | self._data = dict() 48 | self._initialized = False 49 | 50 | if filename: 51 | self._data = self.parse(filename) 52 | self._initialized = True 53 | 54 | def initialize(self): 55 | """ 56 | Initialize the object. 57 | """ 58 | self._template = None 59 | self._data = dict() 60 | 61 | @property 62 | def data(self): 63 | """ 64 | Returns the parsed metadata. 65 | 66 | :returns: parsed metadata. 67 | :rtype: str 68 | """ 69 | import simplejson as json 70 | return json.dumps(self._data, indent=4) 71 | 72 | def parse(self, filename): 73 | """ 74 | Parses a single template file. Data is structured into groups 75 | of attributes (ie: 'Transform', 'Attributes') 76 | 77 | :param str filename: file on disk to read. 78 | 79 | :returns: dictionary of metadata parameters. 80 | :rtype: dict 81 | """ 82 | if self._initialized: 83 | self.initialize() 84 | 85 | log.debug('reading metadata file: "%s"' % filename) 86 | data = dict() 87 | if filename is not None: 88 | if os.path.exists(filename): 89 | 90 | parent = data 91 | attr_name = None 92 | 93 | for line in open(filename,'r'): 94 | 95 | #remove newlines 96 | line = line.rstrip('\n') 97 | rline = line.lstrip(' ') 98 | rline = rline.rstrip() 99 | 100 | if not rline.startswith("#") and not rline.startswith(';') and rline.strip() != "": 101 | # parse sections 102 | # remove leading spaces 103 | 104 | 105 | # section/attribute header match 106 | if re.match(regex.get("section"), rline): 107 | 108 | section_obj = re.search(regex.get("section_value"), rline) 109 | 110 | if section_obj: 111 | section_type = section_obj.group('attr') 112 | section_value = section_obj.group('value') 113 | 114 | # parse groups 115 | if section_type == 'group': 116 | if section_value not in parent: 117 | parent = data 118 | group_data = dict() 119 | # set the current parent 120 | parent[section_value] = group_data 121 | parent = parent[section_value] 122 | #print '\nGroup: "%s"' % section_value 123 | 124 | if section_type == 'attr': 125 | attr_data = dict() 126 | # connection attributes 127 | #attr_data.update(connectable=False) 128 | #attr_data.update(connection_type=None) 129 | parent[section_value] = attr_data 130 | attr_name = section_value 131 | #print ' Attribute: "%s"' % attr_name 132 | 133 | if section_type in ['input', 'output']: 134 | conn_data = dict() 135 | conn_data.update(connectable=True) 136 | conn_data.update(connection_type=section_type) 137 | parent[section_value] = conn_data 138 | attr_name = section_value 139 | #print ' Connection: "%s"' % attr_name 140 | 141 | else: 142 | prop_obj = re.search(regex.get("properties"), rline) 143 | 144 | if prop_obj: 145 | 146 | pname = prop_obj.group('name') 147 | ptype = prop_obj.group('type') 148 | pvalu = prop_obj.group('value') 149 | 150 | #print 'property: "%s" (%s)' % (pname, rline) 151 | value = pvalu 152 | if ptype in ['BOOL', 'INPUT', 'OUTPUT']: 153 | if ptype == 'BOOL': 154 | value = True if pvalu == 'true' else False 155 | 156 | # return connection types 157 | if ptype in ['INPUT', 'OUTPUT']: 158 | 159 | # data type: pvalu = FILE, DIRECTORY, ETC. 160 | value = pvalu.lower() 161 | 162 | # try and get the actual value 163 | else: 164 | try: 165 | value = eval(pvalu) 166 | except: 167 | log.warning('cannot parse default value of "%s.%s": "%s" (%s)' % (attr_name, pname, pvalu, filename)) 168 | #print ' property: %s (%s)' % (prop_obj.group('name'), attr_name) 169 | properties = {pname: {'type':ptype, 'value':value}} 170 | parent[attr_name].update(properties) 171 | else: 172 | if rline: 173 | log.debug('skipping: "%s"' % rline) 174 | return data 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/data/__init__.py -------------------------------------------------------------------------------- /data/globals.ini: -------------------------------------------------------------------------------- 1 | # application defaults 2 | [globals] 3 | ignore_scene_prefs = false 4 | logging_level = 30 5 | autosave_inc = 90000 6 | stylesheet_name = default 7 | use_gl = false 8 | 9 | [scene] 10 | edge_type = bezier 11 | render_fx = true 12 | antialiasing = 2 13 | -------------------------------------------------------------------------------- /data/main_menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu_file": { 3 | "label": "File", 4 | "shortcut": "F", 5 | "tooltip": "", 6 | "action_new_graph": { 7 | "label": "New graph", 8 | "tooltip": "Clear the graph.", 9 | "shortcut": { 10 | "key": "N", 11 | "modifier": "Ctrl" 12 | }, 13 | "action": { 14 | "command": "SceneGraph.resetGraph", 15 | "args": [], 16 | "kwargs": {} 17 | } 18 | }, 19 | "separator1": "SEPARATOR", 20 | "action_read_graph": { 21 | "label": "Open graph...", 22 | "tooltip": "Open a scene.", 23 | "shortcut": { 24 | "key": "O", 25 | "modifier": "Ctrl" 26 | }, 27 | "action": { 28 | "command": "SceneGraph.readGraph", 29 | "args": [], 30 | "kwargs": {} 31 | } 32 | }, 33 | "separator2": "SEPARATOR", 34 | "action_save_graph": { 35 | "label": "Save graph...", 36 | "shortcut": { 37 | "key": "S", 38 | "modifier": "Ctrl" 39 | }, 40 | "tooltip": "Save current scene.", 41 | "action": { 42 | "command": "SceneGraph.saveCurrentGraph", 43 | "args": [], 44 | "kwargs": {} 45 | } 46 | }, 47 | "action_save_graph_as": { 48 | "label": "Save graph as...", 49 | "shortcut": {}, 50 | "tooltip": "Save current scene as a new scene.", 51 | "action": { 52 | "command": "SceneGraph.saveGraphAs", 53 | "args": [], 54 | "kwargs": {} 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /data/scene_tab_menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "context_menu": { 3 | "Add object": { 4 | "alembic": "cmd", 5 | "camera": "cmd", 6 | "channel": "cmd" 7 | }, 8 | "Add asset": { 9 | "published asset": "cmd", 10 | "dependency": "cmd", 11 | "connection": "cmd" 12 | }, 13 | "Operation": { 14 | "merge": "cmd", 15 | "export": "cmd", 16 | "import": "cmd" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SceneGraph.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SceneGraph.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/SceneGraph" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SceneGraph" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/SceneGraph_setup.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/SceneGraph_setup.dia -------------------------------------------------------------------------------- /doc/images/EdgeWidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/EdgeWidget.png -------------------------------------------------------------------------------- /doc/images/NodeWidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/NodeWidget.png -------------------------------------------------------------------------------- /doc/images/add_attribute_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/add_attribute_dialog.png -------------------------------------------------------------------------------- /doc/images/add_node_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/add_node_menu.png -------------------------------------------------------------------------------- /doc/images/attr_editor_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/attr_editor_dot.png -------------------------------------------------------------------------------- /doc/images/autosave_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/autosave_time.png -------------------------------------------------------------------------------- /doc/images/drag_edge01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/drag_edge01.png -------------------------------------------------------------------------------- /doc/images/edge_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/edge_type.png -------------------------------------------------------------------------------- /doc/images/expand_node_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/expand_node_check.png -------------------------------------------------------------------------------- /doc/images/goals_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/goals_note.png -------------------------------------------------------------------------------- /doc/images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/intro.png -------------------------------------------------------------------------------- /doc/images/intro_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/intro_debug.png -------------------------------------------------------------------------------- /doc/images/layouts_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/layouts_add.png -------------------------------------------------------------------------------- /doc/images/layouts_restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/layouts_restore.png -------------------------------------------------------------------------------- /doc/images/node_expand_animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/node_expand_animated.gif -------------------------------------------------------------------------------- /doc/images/output_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/output_terminal.png -------------------------------------------------------------------------------- /doc/images/plugins_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/plugins_manager.png -------------------------------------------------------------------------------- /doc/images/plugins_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/plugins_menu.png -------------------------------------------------------------------------------- /doc/images/prefs_pane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/prefs_pane.png -------------------------------------------------------------------------------- /doc/images/render_fx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/render_fx.png -------------------------------------------------------------------------------- /doc/images/src/default_colors.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/default_colors.xcf -------------------------------------------------------------------------------- /doc/images/src/drag_edge01.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/drag_edge01.xcf -------------------------------------------------------------------------------- /doc/images/src/edge_type.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/edge_type.xcf -------------------------------------------------------------------------------- /doc/images/src/intro.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/intro.xcf -------------------------------------------------------------------------------- /doc/images/src/node_expand01.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/node_expand01.xcf -------------------------------------------------------------------------------- /doc/images/src/node_expand_animated.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/node_expand_animated.xcf -------------------------------------------------------------------------------- /doc/images/src/render_fx.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/doc/images/src/render_fx.xcf -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SceneGraph.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SceneGraph.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/source/api.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | SceneGraph API 3 | ============== 4 | 5 | The SceneGraph API allows you to interact with graphs without using the UI. 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 4 10 | 11 | api/core 12 | api/ui 13 | api/util 14 | -------------------------------------------------------------------------------- /doc/source/api/core.rst: -------------------------------------------------------------------------------- 1 | Core Modules 2 | ============ 3 | 4 | The core modules represent the core of the application (duh). The graph, plugins and node types are all accessible from here. Node widgets and other UI modules are intentially separate so that the core module can be manipulated indepedantly of the user interface. 5 | 6 | :: 7 | 8 | from SceneGraph import core 9 | g=core.Graph() 10 | 11 | n1=g.addNode('default') 12 | n2=g.addNode('default') 13 | 14 | e1=g.addEdge(n1, n2) 15 | 16 | 17 | .. automodule:: core.graph 18 | 19 | core.graph 20 | ---------- 21 | 22 | .. _Graph: 23 | 24 | Graph 25 | ^^^^^ 26 | 27 | The Graph class is a wrapper for a networkx.MultiDiGraph graph. The Graph can be fully manipulated without ever opening the UI. Under the hood, the Graph stores nodes as queryable :ref:`DagNode` objects. Edges are stored a little differently; they are stored as NetworkX graph edges only. 28 | 29 | .. autoclass:: Graph 30 | :members: 31 | 32 | 33 | .. _Grid: 34 | 35 | Grid 36 | ^^^^ 37 | 38 | Defines a two-dimensional grid used by the graph to auto-space new nodes. If the graph is in UI mode, the grid is not used, relying on the mouse position to determine the initial value of a new node. 39 | 40 | To setup a new Grid, initialize an instance with 5 rows & 5 columns, with a row width & height of 100: 41 | 42 | 43 | .. testsetup:: 44 | 45 | from SceneGraph.core.graph import Grid 46 | 47 | .. testcode:: 48 | 49 | from SceneGraph.core.graph import Grid 50 | g = Grid(5, 5, width=100, height=100) 51 | print g.coords 52 | 53 | Printing the :func:`Graph.coords ` will give you the current coordinates. 54 | 55 | .. testoutput:: 56 | 57 | (0, 0) 58 | 59 | To advance to the next cell, simple call the :func:`Graph.next() ` method: 60 | 61 | .. testcode:: 62 | 63 | g.next() 64 | print g.coords 65 | 66 | Print the :func:`Graph.coords ` again to see the new coordinates: 67 | 68 | 69 | .. testoutput:: 70 | 71 | (0, 100) 72 | 73 | .. autoclass:: Grid 74 | :members: 75 | 76 | 77 | core.attributes 78 | --------------- 79 | 80 | .. _Attribute: 81 | 82 | Attribute 83 | ^^^^^^^^^ 84 | 85 | The :ref:`Attribute` class is a generic connection node for DagNode objects. To query a node's connections: 86 | :: 87 | 88 | #!/usr/bin/env python 89 | node = g.get_node('model1')[0] 90 | conn = node.getConnection('output') 91 | output = node.dagnode.attributes('output') 92 | 93 | Attributes are stored as dictionaries of connection name/connection attributes. Attributes are stored in a special dictionary in the scene: 94 | :: 95 | 96 | "output": { 97 | "value": null, 98 | "_edges": [], 99 | "attr_type": "null", 100 | "connectable": true, 101 | "connection_type": "output" 102 | } 103 | 104 | The name of this connection is **output** and the **connection_type** denotes it as an output connection. Therefore it will be rendered as a green output node on the right side of the parent node. 105 | 106 | .. automodule:: core.attributes 107 | .. autoclass:: Attribute 108 | :members: 109 | 110 | 111 | core.plugins 112 | ------------ 113 | 114 | .. _PluginManager: 115 | 116 | PluginManager 117 | ^^^^^^^^^^^^^ 118 | The PluginManager manages how SceneGraph finds and loads plugins. 119 | 120 | .. automodule:: core.plugins 121 | .. autoclass:: PluginManager 122 | :members: 123 | 124 | 125 | core.metadata 126 | ------------- 127 | 128 | .. _MetadataParser: 129 | 130 | MetadataParser 131 | ^^^^^^^^^^^^^^ 132 | The MetadataParser reads and translates metadata to the widget. 133 | 134 | .. automodule:: core.metadata 135 | .. autoclass:: MetadataParser 136 | :members: 137 | 138 | 139 | core.nodes 140 | ---------- 141 | .. automodule:: core.nodes 142 | :members: 143 | :undoc-members: 144 | :show-inheritance: 145 | 146 | .. _Node: 147 | 148 | Node 149 | ^^^^ 150 | The basic DAG node type. Represents a dag node that has attributes, but no connections. Currently, the only node 151 | that utilizes this type is the Note. 152 | 153 | .. autoclass:: Node 154 | :members: 155 | 156 | .. _DagNode: 157 | 158 | DagNode 159 | ^^^^^^^ 160 | The DagNode class is the base class for all nodes. Allows for custom attributes to be added, as well as connections. 161 | 162 | .. autoclass:: DagNode 163 | :members: 164 | 165 | .. _DefaultNode: 166 | 167 | DefaultNode 168 | ^^^^^^^^^^^ 169 | The DefaultNode is a standard node with one input and one output. 170 | 171 | .. autoclass:: DefaultNode 172 | :members: 173 | 174 | 175 | .. _DotNode: 176 | 177 | DotNode 178 | ^^^^^^^ 179 | The DotNode is a standard node with one input and one output. Useful for directing the graph in different directions. Dot nodes don't accept new attributes nor do they expand. 180 | 181 | .. autoclass:: DotNode 182 | :members: 183 | 184 | 185 | .. _NoteNode: 186 | 187 | NoteNode 188 | ^^^^^^^^ 189 | The NoteNode is a standard node with one no inputs or outputs. It simply displays a single text string attribute. Useful for adding notes to other users opening scenes. 190 | 191 | .. autoclass:: NoteNode 192 | :members: 193 | 194 | 195 | .. _Metadata: 196 | 197 | Metadata 198 | ^^^^^^^^ 199 | The Metadata parses node metadata on disk. 200 | 201 | .. autoclass:: Metadata 202 | :members: 203 | 204 | 205 | core.events 206 | ----------- 207 | .. automodule:: core.events 208 | 209 | .. _EventHandler: 210 | 211 | EventHandler 212 | ^^^^^^^^^^^^ 213 | The EventHandler object is a flexible class to manage any object's callbacks. The primary useage is to send updates from the :ref:`Graph` object to the :ref:`SceneEventHandler` object. 214 | 215 | To create a signal, simply create an object and add an EventHandler as an attribute. Make sure to pass the parent object as the first object as the parent is **always** the first argument passed to the callback functions. 216 | 217 | 218 | .. testsetup:: 219 | 220 | from SceneGraph.core.events import EventHandler 221 | 222 | .. testcode:: 223 | 224 | from SceneGraph.core.events import EventHandler 225 | from SceneGraph.core.graph import Graph 226 | 227 | g = Graph() 228 | g.evaluated = EventHandler(g) 229 | 230 | 231 | # create a callback function 232 | def evaluatedCallback(graph, evaluated): 233 | if evaluated: 234 | print '# graph evaluated.' 235 | 236 | # create a callback function 237 | def anotherCallback(graph, evaluated): 238 | if evaluated: 239 | print '# graph nodes: ', len(graph.nodes()) 240 | 241 | # connect the callbacks to the handler 242 | g.evaluated += evaluatedCallback 243 | g.evaluated += anotherCallback 244 | 245 | 246 | Calling the signal runs any connected callbacks: 247 | 248 | .. testcode:: 249 | 250 | # call the signal to update all of the connected functions 251 | g.evaluated(True) 252 | 253 | 254 | .. testoutput:: 255 | 256 | # graph evaluated. 257 | # graph nodes: 0 258 | 259 | 260 | To temporarily block the callback from signalling its observers, call the :func:`EventHandler.blockSignals() ` method: 261 | 262 | .. testcode:: 263 | 264 | g.evaluated.blockSignals(True) 265 | 266 | 267 | .. autoclass:: EventHandler 268 | :members: -------------------------------------------------------------------------------- /doc/source/api/grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.graph import Grid 3 | g = Grid(10, 10, width=100, height=100) 4 | print g.coords 5 | g.next() 6 | print g.coords -------------------------------------------------------------------------------- /doc/source/api/ui.rst: -------------------------------------------------------------------------------- 1 | 2 | UI Modules 3 | ========== 4 | 5 | The UI modules represent the user iterface side of the application. All widgets and handlers for the UI are stored here. 6 | 7 | 8 | .. automodule:: ui.commands 9 | 10 | ui.commands 11 | ----------- 12 | 13 | .. _SceneNodesCommand: 14 | 15 | SceneNodesCommand 16 | ^^^^^^^^^^^^^^^^^ 17 | The SceneNodesCommand is a command to send a node/scene data state to the Undo stack. 18 | 19 | .. autoclass:: SceneNodesCommand 20 | :members: 21 | :special-members: 22 | :private-members: 23 | 24 | 25 | .. _SceneChangedCommand: 26 | 27 | SceneChangedCommand 28 | ^^^^^^^^^^^^^^^^^^^ 29 | The SceneChangedCommand is a command to send a node/scene data state to the Undo stack. 30 | 31 | .. autoclass:: SceneChangedCommand 32 | :members: 33 | :special-members: 34 | :private-members: 35 | 36 | .. automodule:: ui.graphics 37 | 38 | ui.graphics 39 | ----------- 40 | 41 | .. _GraphException: 42 | 43 | GraphException 44 | ^^^^^^^^^^^^^^ 45 | Custom exception class. 46 | 47 | .. autoclass:: GraphException 48 | :members: 49 | 50 | 51 | .. _GraphicsView: 52 | 53 | GraphicsView 54 | ^^^^^^^^^^^^ 55 | Custom QGraphicsView for use with SceneGraph. 56 | 57 | .. autoclass:: GraphicsView 58 | :members: 59 | 60 | 61 | .. _GraphicsScene: 62 | 63 | GraphicsScene 64 | ^^^^^^^^^^^^^ 65 | Custom QGraphicsScene for use with SceneGraph. You can query any node widget via the GraphicsScene object: 66 | :: 67 | 68 | #!/usr/bin/env python 69 | node = ui.scene.get_node('default1') 70 | 71 | # query the node connections 72 | node.connections.keys() 73 | 74 | # get the output connection of node 75 | conn = node.getConnection('output') 76 | 77 | .. autoclass:: GraphicsScene 78 | :members: 79 | 80 | 81 | .. automodule:: ui.handlers 82 | 83 | ui.handlers 84 | ----------- 85 | 86 | .. _SceneEventHandler: 87 | 88 | SceneEventHandler 89 | ^^^^^^^^^^^^^^^^^ 90 | The SceneEventHandler connects the Graph object to the :ref:`GraphicsScene` instance. Data is shuttled via callbacks back and forth. 91 | 92 | .. autoclass:: SceneEventHandler 93 | :members: 94 | 95 | 96 | .. automodule:: ui.settings 97 | 98 | ui.settings 99 | ----------- 100 | 101 | .. _Settings: 102 | 103 | Settings 104 | ^^^^^^^^ 105 | The Settings is a custom QSettings object for managing SceneGraph user preferences. 106 | 107 | .. autoclass:: Settings 108 | :members: 109 | 110 | 111 | ui.stylesheet 112 | ------------- 113 | .. _StylesheetManager: 114 | 115 | StylesheetManager 116 | ^^^^^^^^^^^^^^^^^ 117 | The StylesheetManager parses stylesheets and font/color preferences. 118 | 119 | .. automodule:: ui.stylesheet 120 | .. autoclass:: StylesheetManager 121 | :members: 122 | 123 | 124 | .. automodule:: ui.node_widgets 125 | 126 | .. _node_widgets: 127 | 128 | ui.node_widgets 129 | --------------- 130 | The **node_widgets** module contains all of the node widgets used for drawing nodes in the graph. The current types are: 131 | 132 | - :ref:`NodeWidget` 133 | - :ref:`EdgeWidget` 134 | - :ref:`Connection` 135 | 136 | .. _NodeWidget: 137 | 138 | NodeWidget 139 | ^^^^^^^^^^ 140 | 141 | .. image:: ../../images/NodeWidget.png 142 | 143 | The NodeWidget is the base class for all node widgets. NodeWidgets are custom :ref:`QtGui.QGraphicsObject` that contain a reference to their referenced `DagNode`. The NodeWidget *must* be instantiated with the DagNode as the first argument: 144 | 145 | :: 146 | 147 | g = core.Graph() 148 | dag = g.add_node('default') 149 | widget = NodeWidget(dag) 150 | 151 | 152 | The NodeWidget reads its base attributes from the DagNode, and conversely, updates are passed back to the DagNode. 153 | 154 | .. autoclass:: NodeWidget 155 | :members: 156 | 157 | 158 | NodeLabel 159 | ^^^^^^^^^ 160 | The NodeLabel draws the node name. 161 | 162 | .. autoclass:: NodeLabel 163 | :members: 164 | 165 | .. _NodeBackground: 166 | 167 | NodeBackground 168 | ^^^^^^^^^^^^^^ 169 | The :ref:`NodeBackground` draws the node background. 170 | 171 | .. autoclass:: NodeBackground 172 | :members: 173 | 174 | .. _EdgeWidget: 175 | 176 | EdgeWidget 177 | ^^^^^^^^^^ 178 | The EdgeWidget is the base class for edge widgets: 179 | 180 | .. image:: ../../images/EdgeWidget.png 181 | 182 | .. autoclass:: EdgeWidget 183 | :members: 184 | 185 | .. _Connection: 186 | 187 | Connection 188 | ^^^^^^^^^^ 189 | The Connection defines connections between nodes. 190 | 191 | .. autoclass:: Connection 192 | :members: -------------------------------------------------------------------------------- /doc/source/api/util.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | Utilities and functions for the entire UI are stored here. 5 | 6 | .. _utils: 7 | 8 | .. automodule:: util 9 | :members: -------------------------------------------------------------------------------- /doc/source/extending.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Extending SceneGraph 3 | ==================== 4 | 5 | .. image:: ../images/intro_debug.png 6 | 7 | Extending SceneGraph is very easy. Custom plugins are easy to write and behave well with the builtin node types. The SceneGraph API provides distinct node classes that can be created: 8 | 9 | - Execute 10 | - Math 11 | - Read/Write 12 | - Mapping 13 | - File 14 | - Output 15 | - Globals 16 | - Display 17 | - Container 18 | 19 | Node Classes 20 | ============ 21 | 22 | Coming soon. 23 | 24 | Writing your own plugins 25 | ======================== 26 | 27 | To write your own plugins, you'll need three things: 28 | 29 | - DagNode object file 30 | - NodeWidget object file 31 | - Metadata attribute description file (optional) 32 | 33 | You'll need to subclass the default :ref:`DagNode` object type, as well as a corresponding :ref:`NodeWidget` type. 34 | 35 | Plugin Files 36 | ------------ 37 | 38 | Place your custom plugin somewhere in the SCENEGRAPH_PLUGIN_PATH_. You'll need to subclass the core :ref:`DagNode` class, and add your functionality on top of that. Let's start with a simple node: 39 | 40 | :: 41 | 42 | #!/usr/bin/env python 43 | from SceneGraph import options 44 | from SceneGraph.core.nodes import DagNode 45 | 46 | 47 | class MyNode(DagNode): 48 | node_type = 'myNode' 49 | node_class = 'container' 50 | default_name = 'my_node' 51 | default_color = [172, 172, 172, 255] 52 | 53 | def __init__(self, name=None, **kwargs): 54 | DagNode.__init__(self, name, **kwargs) 55 | 56 | 57 | To register the node as a valid type, you'll need to add attributes for the class: **node_type** and **node_class**. Additionally, you can add a descriptor attribute **node_category**. 58 | 59 | After that, let's create a widget plugin: 60 | 61 | 62 | :: 63 | 64 | #!/usr/bin/env python 65 | from SceneGraph.ui.node_widgets import NodeWidget 66 | 67 | 68 | class MyNodeWidget(NodeWidget): 69 | widget_type = 'myNode' 70 | node_class = 'container' 71 | def __init__(self, dagnode, parent=None): 72 | NodeWidget.__init__(self, dagnode, parent) 73 | 74 | 75 | You'll need to make sure that the widget has an attribute **widget_type** that matches the base node type you've just created. 76 | 77 | 78 | Metadata Description Files 79 | -------------------------- 80 | The metadata is used to describe your node's parameters to the application. You'll need to define attributes and groups. Private attributes will not show in the UI by default. Each node will inherit all of its parent classes metadata descriptors, so you won't have to manage parent attributes unless you choose to. 81 | 82 | :: 83 | 84 | # dot node attributes 85 | [group Node Transform] 86 | 87 | [attr width] 88 | default FLOAT 8.0 89 | required BOOL true 90 | private BOOL true 91 | 92 | [attr radius] 93 | default FLOAT 8.0 94 | label STRING "dot radius" 95 | required BOOL true 96 | 97 | 98 | The above metadata is the builtin **Dot** node's description. Rendered in the **AttributeEditor**, it looks like this: 99 | 100 | .. image:: ../images/attr_editor_dot.png 101 | 102 | Under the **Node Transform** group, we see the **Position** attribute. That attribute is inherited from the parent :ref:`DagNode` object. If we add it to the descriptor above and set the **private** paremeter, it will no longer render in the **AttributeEditor**: 103 | 104 | :: 105 | 106 | # dot node attributes 107 | [group Node Transform] 108 | 109 | [attr pos] 110 | private BOOL true 111 | 112 | [attr width] 113 | default FLOAT 8.0 114 | required BOOL true 115 | private BOOL true 116 | 117 | [attr radius] 118 | default FLOAT 8.0 119 | label STRING "dot radius" 120 | required BOOL true 121 | 122 | 123 | The **group** determines which group the attributes will be grouped under. Note that the **width** attribute is not shown, while the **radius** is. Setting the **width.private** paramenter to **false** will allow the user to change it. 124 | 125 | Warning: exposing private :ref:`DagNode` attributes can lead to system unstability. It is strongly recommended that you do not do that. 126 | 127 | Building Plugins 128 | ---------------- 129 | 130 | After your plugin is ready, run the utility **/bin/build_plugin** on your files to create the plugin file. 131 | 132 | 133 | Environment Setup 134 | ================= 135 | 136 | To extend SceneGraph, there are a few environment variables we'll need to set: 137 | 138 | .. _SCENEGRAPH_PLUGIN_PATH: 139 | 140 | SCENEGRAPH_PLUGIN_PATH 141 | ---------------------- 142 | 143 | - Use this variable to extend where SceneGraph goes to look for node plugins. 144 | 145 | 146 | SCENEGRAPH_CONFIG_PATH 147 | ---------------------- 148 | 149 | - Use this variable to add different font and color schemes for the UI. 150 | 151 | 152 | SCENEGRAPH_STYLESHEET_PATH 153 | -------------------------- 154 | 155 | - Use this variable to add different Qt stylesheets for skinning the UI. -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. SceneGraph documentation master file, created by 2 | sphinx-quickstart on Wed Aug 5 08:13:03 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. Header1 7 | ======= 8 | 9 | Header2 10 | ------- 11 | 12 | Header3 13 | ^^^^^^^ 14 | 15 | Header4 16 | ~~~~~~~ 17 | 18 | SceneGraph v |version| documentation 19 | ================================== 20 | 21 | **SceneGraph** is a fast & flexible framework for defining node graphs using PySide. Scenes can be saved and loaded in a variety of applications, and users can easily add their own nodes to suit their needs. 22 | 23 | .. image:: ../images/intro.png 24 | 25 | 26 | Supported Systems 27 | ----------------- 28 | **SceneGraph** works on Linux, OSX* and Windows platforms. It currently supports the following DCC platforms: 29 | 30 | - Maya 2015 & 2016 31 | - Nuke 8 & 9 32 | - Houdini 14 & 15 33 | 34 | Applications that only support Python 2.6 are not supported. 35 | 36 | \* OSX note: PySide performance on OSX 10.10 may experience slowdowns compared to Linux or Windows. OSX 10.11 is **highly** recommended. 37 | 38 | 39 | Requirements 40 | ------------ 41 | 42 | - Python 2.7 43 | - `NetworkX 1.9.1`_ 44 | - `simplejson 3.7.2`_ 45 | - PySide 1.2 46 | 47 | .. _NetworkX 1.9.1: https://github.com/networkx/networkx 48 | .. _simplejson 3.7.2: https://github.com/simplejson/simplejson 49 | 50 | 51 | Contents 52 | -------- 53 | .. toctree:: 54 | :maxdepth: 3 55 | 56 | overview 57 | tutorial 58 | api 59 | extending 60 | 61 | 62 | 63 | Indices and tables 64 | ================== 65 | 66 | * :ref:`genindex` 67 | * :ref:`modindex` 68 | * :ref:`search` 69 | 70 | -------------------------------------------------------------------------------- /doc/source/overview.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Overview 3 | ======== 4 | 5 | .. image:: ../images/goals_note.png 6 | 7 | History 8 | ======= 9 | 10 | SceneGraph was built to fascilitate a graphical interface for custom visual effects pipeline tools. 11 | 12 | Goals 13 | ===== 14 | 15 | SceneGraph is intended to provide a lightweight framework for visual node graphs. The goals currently are: 16 | 17 | - fast: using PySide vector graphics allows nodes to be drawn quickly 18 | - flexible: users can easily add their own nodes and functions 19 | - intuitive: node widgets are easy to read and understand 20 | 21 | -------------------------------------------------------------------------------- /doc/source/tutorial.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Tutorial 3 | ======== 4 | 5 | Getting started 6 | =============== 7 | 8 | To start using SceneGraph, simply run **/bin/SceneGraph** from a shell. (Windows users run **/bin/SceneGraph.bat**) 9 | 10 | On Linux and OSX, you can specify a file to open on launch: 11 | 12 | :: 13 | 14 | SceneGraph ~/graphs/my_graph_v001.json 15 | 16 | 17 | Command-line flags 18 | ------------------ 19 | 20 | :: 21 | 22 | # use OpenGL renderer 23 | -g 24 | 25 | # set Qt style 26 | -s @STYLE 27 | 28 | 29 | DCC Applications 30 | ---------------- 31 | 32 | SceneGraph contains modules for Maya, Nuke & Houdini. 33 | 34 | Maya 35 | ^^^^ 36 | :: 37 | 38 | from SceneGraph import scenegraph_maya 39 | scenegraph_maya.main() 40 | 41 | 42 | Nuke 43 | ^^^^ 44 | :: 45 | 46 | from SceneGraph import scenegraph_nuke 47 | scenegraph_nuke.main() 48 | 49 | 50 | Houdini 51 | ^^^^^^^ 52 | 53 | Coming soon. 54 | 55 | 56 | Other Applications 57 | ^^^^^^^^^^^^^^^^^^ 58 | To use SceneGraph in another application, simply import the UI from the standard module: 59 | :: 60 | 61 | from SceneGraph import scenegraph 62 | sgui = scenegraph.SceneGraphUI() 63 | sgui.show() 64 | 65 | 66 | Node Types 67 | ========== 68 | 69 | Default 70 | ------- 71 | 72 | Merge 73 | ----- 74 | 75 | Note 76 | ---- 77 | The :ref:`NoteNode` is simply a resizable display node that can display user's text. 78 | 79 | Dot 80 | --- 81 | The :ref:`DotNode` is a simple node that can change the direction of the graph. 82 | 83 | Working with the Graph 84 | ====================== 85 | 86 | You can delete nodes with the delete key, split nodes by clicking on the midpoint arrow in an edge. Additionally, if you select nodes(s) and drag with the option key pressed, upstream nodes will also be selected and moved. 87 | 88 | Creating Nodes 89 | -------------- 90 | 91 | There are several ways to create a node in the graph: 92 | 93 | - right-click the mouse in the graph area to open the **Add node** menu. 94 | - press the **tab button** to open the **Add node** menu. 95 | - choose a node type from the **Nodes>Add node** menu. 96 | 97 | Choose a node type and it will be placed near where your mouse is positioned. 98 | 99 | .. image:: ../images/add_node_menu.png 100 | 101 | 102 | Connecting Nodes 103 | ---------------- 104 | 105 | To connect two nodes, click on a green output terminal of one node, and drag the mouse to the yellow input terminal of another node. 106 | 107 | .. image:: ../images/drag_edge01.png 108 | 109 | 110 | Expanding & Folding Nodes 111 | ------------------------- 112 | 113 | If you select a node in the :ref:`attr_editor`, you'll see an **Expand Node** checkbox: 114 | 115 | .. image:: ../images/expand_node_check.png 116 | 117 | Expanding a node shows all of its connections in the graph, un-expanding it makes it as small as possible: 118 | 119 | .. image:: ../images/node_expand_animated.gif 120 | 121 | 122 | Viewing Dependencies 123 | -------------------- 124 | 125 | Selected nodes' dependencies are visible in the **Dependencies** pane. 126 | 127 | 128 | Saving & Loading Scenes 129 | ======================= 130 | 131 | Saving and loading is managed through the **File** menu. If the user attempts to close the application when a file has not been saved, they will be prompted to close the appliation without saving. 132 | 133 | Autosaving 134 | ---------- 135 | **SceneGraph** autosaves after a predetermined amount of time. If the user attempts to open a scene that has a newer autosave, they will be prompted to choose opening either the original, or the autosave file (useful in the event of a crash). 136 | 137 | .. _attr_editor: 138 | 139 | Attribute Editor 140 | ================ 141 | 142 | The :ref:`attr_editor` is a powerful tool to interface with nodes in the graph. 143 | 144 | Adding Attributes 145 | ----------------- 146 | 147 | To add an attribute to a node, select it and right-click in the **AttributeEditor** pane. Choose **Add Attribute** which will open a dialog: 148 | 149 | .. image:: ../images/add_attribute_dialog.png 150 | 151 | 152 | Choose a name and type, and click okay to add it. By default it will appear in the **User** group in the **AttributeEditor**. You can also choose to make the attribute connectable, in which case it will show up as a terminal in the graph (user attributes will render with an italicized label). 153 | 154 | 155 | Keyboard Commands 156 | ================= 157 | 158 | +------------+------------+-----------+-------------------------------+ 159 | | Key | Modifier | Modifier | Description | 160 | +============+============+===========+===============================+ 161 | | A | | | fit all nodes in the graph | 162 | +------------+------------+-----------+-------------------------------+ 163 | | D | | | disable selected nodes | 164 | +------------+------------+-----------+-------------------------------+ 165 | | E | | | toggle edge types | 166 | +------------+------------+-----------+-------------------------------+ 167 | | F | | | fit selected nodes in graph | 168 | +------------+------------+-----------+-------------------------------+ 169 | | Tab | | | open the **Add node** menu | 170 | +------------+------------+-----------+-------------------------------+ 171 | | Option | | | split edge with a dot node* | 172 | +------------+------------+-----------+-------------------------------+ 173 | | Option | | | select upstream nodes** | 174 | +------------+------------+-----------+-------------------------------+ 175 | | O | Ctrl | | open a scene from disk | 176 | +------------+------------+-----------+-------------------------------+ 177 | | S | Ctrl | | save the current scene | 178 | +------------+------------+-----------+-------------------------------+ 179 | | Z | Ctrl | | undo the last action | 180 | +------------+------------+-----------+-------------------------------+ 181 | | Z | Ctrl | Shift | redo the last action | 182 | +------------+------------+-----------+-------------------------------+ 183 | 184 | \* mouse must be hovering over the middle of an edge. 185 | 186 | \*\* if pressed while dragging a node, upstream nodes will be selected as well. 187 | 188 | 189 | Plugins 190 | ======= 191 | 192 | Node types are loaded as plugins. New plugins can be added via the :ref:`SCENEGRAPH_PLUGIN_PATH`. variable. 193 | 194 | 195 | Enabling/disabling plugins 196 | -------------------------- 197 | 198 | To open the :ref:`PluginManager`, select the **Windows>Plugins...** menu. 199 | 200 | .. image:: ../images/plugins_menu.png 201 | 202 | The **PluginManager** interface allows the user to enable, disable or load new plugins. The current plugin configuration will be saved to the user's preferences, so on the next launch, **SceneGraph** will only load the current plugins. 203 | 204 | .. image:: ../images/plugins_manager.png 205 | 206 | Preferences 207 | =========== 208 | 209 | **SceneGraph** includes a robust preferences system. Users can save and load UI layouts, as well as customize the graph drawing style to suit their preference. 210 | 211 | .. image:: ../images/prefs_pane.png 212 | 213 | 214 | Viewport Mode 215 | ------------- 216 | 217 | Changing the drawing style can increase draw performance. Options are **full**, **smart** and **minimal**. **Full** will look best, while **minimal** will draw faster, but might briefly display some artifacts when updating the scene. **Smart** is the default. 218 | 219 | Edge Types 220 | ---------- 221 | 222 | Edges can be rendered as **bezier** or **polygon**. Use polygon mode to increase draw performance. 223 | 224 | .. image:: ../images/edge_type.png 225 | 226 | Render FX 227 | --------- 228 | Unchecking this will turn off FX like dropshadows and glows on nodes, labels and edges. Can be used to increase draw performance. 229 | 230 | .. image:: ../images/render_fx.png 231 | 232 | OpenGL 233 | ------ 234 | 235 | Enable the **OpenGL** option to use OpenGL to render the node graph. 236 | 237 | Autosave 238 | -------- 239 | 240 | In the **Preferences** pane, users can edit the autosave increment (measured in seconds): 241 | 242 | .. image:: ../images/autosave_time.png 243 | 244 | Autosave files are saved alongside the working files, or the **TMPDIR** directory if the file has not yet been saved. 245 | 246 | Stylesheets 247 | ----------- 248 | 249 | This menu displays all of the currently loaded stylesheets, and allows the user to update the style on the fly. 250 | 251 | Layouts 252 | ------- 253 | 254 | You can save and load UI layouts in the **Windows** menu. To save a layout, select the **Windows>Save layout** menu option and input a name into the dialog: 255 | 256 | .. image:: ../images/layouts_add.png 257 | 258 | Restore a layout from the **Windows>Restore layout** menu: 259 | 260 | .. image:: ../images/layouts_restore.png 261 | 262 | -------------------------------------------------------------------------------- /icn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/__init__.py -------------------------------------------------------------------------------- /icn/graph_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/graph_icon.png -------------------------------------------------------------------------------- /icn/scenegraph.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/sizegrip.png 4 | icons/checkbox-off.png 5 | icons/checkbox-on.png 6 | icons/spin-arrow-flat-down.png 7 | icons/spin-arrow-flat-up.png 8 | icons/arrow-flat-down.png 9 | icons/arrow-flat-left.png 10 | icons/arrow-flat-up.png 11 | icons/arrow-flat-right.png 12 | icons/spin-arrow-flat-down-hover.png 13 | icons/spin-arrow-flat-down-pressed.png 14 | icons/spin-arrow-flat-up-pressed.png 15 | icons/spin-arrow-flat-up-hover.png 16 | icons/ui-dock-close-off.png 17 | icons/ui-dock-close-on.png 18 | icons/ui-dock-float-on.png 19 | icons/ui-dock-float-off.png 20 | icons/folder-horizontal-open.png 21 | icons/arrow-curve-000-double.png 22 | icons/arrow-curve-000-left.png 23 | icons/arrow-curve-090-left.png 24 | icons/arrow-curve-090.png 25 | icons/arrow-curve-180-double.png 26 | icons/arrow-curve-180-left.png 27 | icons/arrow-curve-180.png 28 | icons/arrow-curve-270-left.png 29 | icons/arrow-curve-270.png 30 | icons/arrow-curve.png 31 | icons/home.png 32 | icons/information.png 33 | icons/json.png 34 | icons/network-cloud.png 35 | icons/network-status-away.png 36 | icons/network-status-busy.png 37 | icons/network-status-offline.png 38 | icons/network-status.png 39 | icons/node-delete-child.png 40 | icons/node-delete-next.png 41 | icons/node-delete-previous.png 42 | icons/node-delete.png 43 | icons/node-design.png 44 | icons/node-insert-child.png 45 | icons/node-insert-next.png 46 | icons/node-insert-previous.png 47 | icons/node-insert.png 48 | icons/node-magnifier.png 49 | icons/node-select-all.png 50 | icons/node-select-child.png 51 | icons/node-select-next.png 52 | icons/node-select-previous.png 53 | icons/node-select.png 54 | icons/node.png 55 | icons/plug--arrow.png 56 | icons/plug--exclamation.png 57 | icons/plug--minus.png 58 | icons/plug--pencil.png 59 | icons/plug--plus.png 60 | icons/plug-connect.png 61 | icons/plug-disconnect-prohibition.png 62 | icons/plug-disconnect.png 63 | icons/plug.png 64 | icons/status-away.png 65 | icons/status-busy.png 66 | icons/status-offline.png 67 | icons/status.png 68 | icons/terminal--arrow.png 69 | icons/terminal--plus.png 70 | icons/terminal-medium.png 71 | icons/terminal-network.png 72 | icons/tick-circle.png 73 | icons/toggle-small-expand.png 74 | icons/toggle-small.png 75 | icons/ui-accordion.png 76 | icons/ui-address-bar-green.png 77 | icons/ui-address-bar-lock.png 78 | icons/ui-address-bar-red.png 79 | icons/ui-address-bar-yellow.png 80 | icons/ui-address-bar.png 81 | icons/ui-breadcrumb-bread.png 82 | icons/ui-breadcrumb-select-current.png 83 | icons/ui-breadcrumb-select-parent.png 84 | icons/ui-breadcrumb-select.png 85 | icons/ui-breadcrumb.png 86 | icons/ui-button-default.png 87 | icons/ui-button-image.png 88 | icons/ui-button-navigation-back.png 89 | icons/ui-button-navigation.png 90 | icons/ui-button-toggle.png 91 | icons/ui-button.png 92 | icons/ui-buttons.png 93 | icons/ui-check-box-mix.png 94 | icons/ui-check-box-uncheck.png 95 | icons/ui-check-box.png 96 | icons/ui-check-boxes-list.png 97 | icons/ui-check-boxes-series.png 98 | icons/ui-check-boxes.png 99 | icons/ui-color-picker-default.png 100 | icons/ui-color-picker-switch.png 101 | icons/ui-color-picker-transparent.png 102 | icons/ui-color-picker.png 103 | icons/ui-combo-box-blue.png 104 | icons/ui-combo-box-calendar.png 105 | icons/ui-combo-box-edit.png 106 | icons/ui-combo-box.png 107 | icons/ui-combo-boxes.png 108 | icons/ui-flow.png 109 | icons/ui-group-box.png 110 | icons/ui-label-link.png 111 | icons/ui-label.png 112 | icons/ui-labels.png 113 | icons/ui-layered-pane.png 114 | icons/ui-layout-panel.png 115 | icons/ui-list-box-blue.png 116 | icons/ui-list-box.png 117 | icons/ui-menu-blue.png 118 | icons/ui-menu.png 119 | icons/ui-paginator.png 120 | icons/ui-panel-resize-actual.png 121 | icons/ui-panel-resize.png 122 | icons/ui-panel.png 123 | icons/ui-progress-bar-indeterminate.png 124 | icons/ui-progress-bar.png 125 | icons/ui-radio-button-uncheck.png 126 | icons/ui-radio-button.png 127 | icons/ui-radio-buttons-list.png 128 | icons/ui-radio-buttons.png 129 | icons/ui-ruler.png 130 | icons/ui-scroll-bar-horizontal.png 131 | icons/ui-scroll-bar.png 132 | icons/ui-scroll-pane-block.png 133 | icons/ui-scroll-pane-blog.png 134 | icons/ui-scroll-pane-both.png 135 | icons/ui-scroll-pane-detail.png 136 | icons/ui-scroll-pane-form.png 137 | icons/ui-scroll-pane-horizontal.png 138 | icons/ui-scroll-pane-icon.png 139 | icons/ui-scroll-pane-image.png 140 | icons/ui-scroll-pane-list.png 141 | icons/ui-scroll-pane-table.png 142 | icons/ui-scroll-pane-text-image.png 143 | icons/ui-scroll-pane-text.png 144 | icons/ui-scroll-pane-tree.png 145 | icons/ui-scroll-pane.png 146 | icons/ui-search-field.png 147 | icons/ui-seek-bar-050.png 148 | icons/ui-seek-bar-100.png 149 | icons/ui-seek-bar.png 150 | icons/ui-separator-label.png 151 | icons/ui-separator.png 152 | icons/ui-slider-050.png 153 | icons/ui-slider-100.png 154 | icons/ui-slider-vertical-050.png 155 | icons/ui-slider-vertical-100.png 156 | icons/ui-slider-vertical.png 157 | icons/ui-slider.png 158 | icons/ui-spacer.png 159 | icons/ui-spin.png 160 | icons/ui-split-panel-vertical.png 161 | icons/ui-split-panel.png 162 | icons/ui-splitter-horizontal.png 163 | icons/ui-splitter.png 164 | icons/ui-status-bar-blue.png 165 | icons/ui-status-bar.png 166 | icons/ui-tab--arrow.png 167 | icons/ui-tab--exclamation.png 168 | icons/ui-tab--minus.png 169 | icons/ui-tab--pencil.png 170 | icons/ui-tab--plus.png 171 | icons/ui-tab-bottom.png 172 | icons/ui-tab-content-vertical.png 173 | icons/ui-tab-content.png 174 | icons/ui-tab-side.png 175 | icons/ui-tab.png 176 | icons/ui-text-area.png 177 | icons/ui-text-field-clear-button.png 178 | icons/ui-text-field-clear.png 179 | icons/ui-text-field-format.png 180 | icons/ui-text-field-hidden.png 181 | icons/ui-text-field-medium-select.png 182 | icons/ui-text-field-medium.png 183 | icons/ui-text-field-password-green.png 184 | icons/ui-text-field-password-red.png 185 | icons/ui-text-field-password-yellow.png 186 | icons/ui-text-field-password.png 187 | icons/ui-text-field-select.png 188 | icons/ui-text-field-small-select.png 189 | icons/ui-text-field-small.png 190 | icons/ui-text-field-suggestion.png 191 | icons/ui-text-field.png 192 | icons/ui-toolbar--arrow.png 193 | icons/ui-toolbar--exclamation.png 194 | icons/ui-toolbar--minus.png 195 | icons/ui-toolbar--pencil.png 196 | icons/ui-toolbar--plus.png 197 | icons/ui-toolbar-bookmark.png 198 | icons/ui-toolbar.png 199 | icons/ui-tooltip--arrow.png 200 | icons/ui-tooltip--exclamation.png 201 | icons/ui-tooltip--minus.png 202 | icons/ui-tooltip--pencil.png 203 | icons/ui-tooltip--plus.png 204 | icons/ui-tooltip-balloon-bottom.png 205 | icons/ui-tooltip-balloon.png 206 | icons/ui-tooltip.png 207 | 208 | 209 | -------------------------------------------------------------------------------- /icn/src/arrows-flat.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/arrows-flat.xcf -------------------------------------------------------------------------------- /icn/src/checks.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/checks.xcf -------------------------------------------------------------------------------- /icn/src/graph_icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/graph_icon.xcf -------------------------------------------------------------------------------- /icn/src/maya_2016_style-tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/maya_2016_style-tabs.png -------------------------------------------------------------------------------- /icn/src/maya_2016_style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/maya_2016_style.png -------------------------------------------------------------------------------- /icn/src/node_base_250x180.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Layer 1 20 | 21 | 22 | -------------------------------------------------------------------------------- /icn/src/sizegrip.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/sizegrip.xcf -------------------------------------------------------------------------------- /icn/src/spin-arrows-flat.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/spin-arrows-flat.xcf -------------------------------------------------------------------------------- /icn/src/ui-dock-buttons-assets/ui-dock-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/ui-dock-buttons-assets/ui-dock-buttons.png -------------------------------------------------------------------------------- /icn/src/ui-dock-buttons-converted.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/ui-dock-buttons-converted.xcf -------------------------------------------------------------------------------- /icn/src/ui-dock-buttons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/ui-dock-buttons.psd -------------------------------------------------------------------------------- /icn/src/ui-dock-buttons.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfessenden/SceneGraph/0fa3429059c77c881d1b58b28e89dcb44c609909/icn/src/ui-dock-buttons.xcf -------------------------------------------------------------------------------- /mtd/dagnode.mtd: -------------------------------------------------------------------------------- 1 | # base DagNode metadata 2 | [group Node Transform] 3 | [attr pos] 4 | default FLOAT2 [0.0, 0.0] 5 | required BOOL true 6 | label STRING "Position" 7 | label.0 STRING "x" 8 | label.1 STRING "y" 9 | tooltip STRING "node x,y coordinates" 10 | 11 | [attr width] 12 | default FLOAT 100.0 13 | required BOOL true 14 | label STRING "Width" 15 | 16 | [group Node Attributes] 17 | [attr base_height] 18 | default FLOAT 15.0 19 | required BOOL true 20 | label STRING "Base Height" 21 | desc STRING "node base height (multiplied by the number of connections)." 22 | private BOOL true 23 | 24 | [attr id] 25 | default STRING "" 26 | required BOOL true 27 | label STRING "Node ID" 28 | private BOOL true 29 | 30 | [attr name] 31 | default STRING "dagnode" 32 | required BOOL true 33 | label STRING "Node Name" 34 | 35 | [attr node_type] 36 | default STRING "dagnode" 37 | required BOOL true 38 | label STRING "Node Type" 39 | private BOOL true 40 | 41 | [attr docstring] 42 | default STRING "" 43 | required BOOL true 44 | label STRING "Doc" 45 | desc STRING "node docstring." 46 | 47 | [attr color] 48 | default COLOR [172, 172, 172] 49 | required BOOL true 50 | desc STRING "node background color." 51 | label STRING "Color" 52 | 53 | [attr enabled] 54 | default BOOL true 55 | required BOOL true 56 | label STRING "Enabled" 57 | 58 | [attr force_expand] 59 | default BOOL false 60 | label STRING "Expand Node" 61 | 62 | [output output] 63 | default MULTI "" 64 | -------------------------------------------------------------------------------- /mtd/default.mtd: -------------------------------------------------------------------------------- 1 | # default node attributes 2 | [group Default Attributes] 3 | 4 | [attr docstring] 5 | default STRING "default node" 6 | 7 | [input input] 8 | default MULTI "" 9 | -------------------------------------------------------------------------------- /mtd/dot.mtd: -------------------------------------------------------------------------------- 1 | # dot node attributes 2 | [group Node Transform] 3 | 4 | [attr width] 5 | default FLOAT 8.0 6 | required BOOL true 7 | private BOOL true 8 | 9 | [attr radius] 10 | default FLOAT 8.0 11 | label STRING "dot radius" 12 | required BOOL true 13 | #private BOOL true 14 | 15 | [group Node Attributes] 16 | 17 | [attr base_height] 18 | default FLOAT 8.0 19 | private BOOL true 20 | 21 | [attr force_expand] 22 | default BOOL false 23 | label STRING "Expand Node" 24 | private BOOL true -------------------------------------------------------------------------------- /mtd/note.mtd: -------------------------------------------------------------------------------- 1 | # note node attributes 2 | [group Node Transform] 3 | 4 | [attr width] 5 | default FLOAT 100.0 6 | required BOOL true 7 | private BOOL true 8 | 9 | [group Node Attributes] 10 | 11 | [attr font_size] 12 | default INT 6 13 | label STRING "Font Size" 14 | 15 | [attr base_height] 16 | default FLOAT 75.0 17 | private BOOL true 18 | 19 | [attr doc_text] 20 | default DOC "Sample note text." 21 | label STRING "Text" 22 | 23 | [attr force_expand] 24 | default BOOL false 25 | label STRING "Expand Node" 26 | private BOOL true 27 | 28 | [attr show_name] 29 | default BOOL false 30 | label STRING "Show name" 31 | -------------------------------------------------------------------------------- /options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | 5 | def setup_platform_defaults(): 6 | """ 7 | Setup globals for a specific platform. 8 | """ 9 | import sys 10 | platform = 'Windows' 11 | HOME = os.getenv('HOMEPATH') 12 | 13 | # linux 14 | if 'linux' in sys.platform: 15 | platform = 'Linux' 16 | HOME = os.getenv('HOME') 17 | 18 | # macintosh 19 | elif sys.platform == 'darwin': 20 | platform = 'MacOSX' 21 | HOME = os.getenv('HOME') 22 | 23 | # windows hackery 24 | else: 25 | os.environ['TMPDIR'] = os.getenv('TEMP') 26 | os.environ['HOME'] = HOME 27 | return ( platform, HOME ) 28 | 29 | 30 | PACKAGE = 'SceneGraph' 31 | API_MAJOR_VERSION = 0.69 32 | API_REVISION = 0 33 | API_VERSION = float('%s%s' % (API_MAJOR_VERSION, API_REVISION)) 34 | API_VERSION_AS_STRING = '%.02f.%d' % (API_MAJOR_VERSION, API_REVISION) 35 | PLATFORM = None 36 | API_MINIMUM = 0.64 37 | 38 | # initialize globals 39 | PLATFORM, USER_HOME = setup_platform_defaults() 40 | 41 | SCENEGRAPH_PATH = os.path.dirname(__file__) 42 | SCENEGRAPH_CORE = os.path.join(SCENEGRAPH_PATH, 'core') 43 | SCENEGRAPH_PLUGIN_PATH = os.path.join(SCENEGRAPH_PATH, 'plugins') 44 | SCENEGRAPH_UI = os.path.join(SCENEGRAPH_PATH, 'ui', 'SceneGraph.ui') 45 | SCENEGRAPH_ATTR_EDITOR_UI = os.path.join(SCENEGRAPH_PATH, 'ui', 'designer', 'NodeAttributes.ui') 46 | SCENEGRAPH_ICON_PATH = os.path.join(SCENEGRAPH_PATH, 'icn') 47 | SCENEGRAPH_STYLESHEET_PATH = os.path.join(SCENEGRAPH_PATH, 'qss') 48 | SCENEGRAPH_TEST_PATH = os.path.join(SCENEGRAPH_PATH, 'test') 49 | SCENEGRAPH_CONFIG_PATH = os.path.join(SCENEGRAPH_PATH, 'cfg') 50 | SCENEGRAPH_METADATA_PATH = os.path.join(SCENEGRAPH_PATH, 'mtd') 51 | 52 | SCENEGRAPH_PREFS_PATH = os.path.join(USER_HOME, '.config', PACKAGE) 53 | SCENEGRAPH_USER_WORK_PATH = os.path.join(USER_HOME, 'graphs') 54 | 55 | 56 | 57 | SCENEGRAPH_COLORS = { 58 | 'blush':[246, 202, 203, 255], 59 | 'petal':[247, 170, 189, 255], 60 | 'petunia':[231, 62, 151, 255], 61 | 'deep_pink':[229, 2, 120, 255], 62 | 'melon':[241, 118, 110, 255], 63 | 'pomegranate':[178, 27, 32, 255], 64 | 'poppy_red':[236, 51, 39, 255], 65 | 'orange_red':[240, 101, 53, 255], 66 | 'olive':[174, 188, 43, 255], 67 | 'spring':[227, 229, 121, 255], 68 | 'yellow':[255, 240, 29, 255], 69 | 'mango':[254, 209, 26, 255], 70 | 'cantaloupe':[250, 176, 98, 255], 71 | 'tangelo':[247, 151, 47, 255], 72 | 'burnt_orange':[236, 137, 36, 255], 73 | 'bright_orange':[242, 124, 53, 255], 74 | 'moss':[176, 186, 39, 255], 75 | 'sage':[212, 219, 145, 255], 76 | 'apple':[178, 215, 140, 255], 77 | 'grass':[111, 178, 68, 255], 78 | 'forest':[69, 149, 62, 255], 79 | 'peacock':[21, 140, 167, 255], 80 | 'teal':[24, 157, 193, 255], 81 | 'aqua':[153, 214, 218, 255], 82 | 'violet':[55, 52, 144, 255], 83 | 'deep_blue':[15, 86, 163, 255], 84 | 'hydrangea':[150, 191, 229, 255], 85 | 'sky':[139, 210, 244, 255], 86 | 'dusk':[16, 102, 162, 255], 87 | 'midnight':[14, 90, 131, 255], 88 | 'seaside':[87, 154, 188, 255], 89 | 'poolside':[137, 203, 225, 255], 90 | 'eggplant':[86, 5, 79, 255], 91 | 'lilac':[222, 192, 219, 255], 92 | 'chocolate':[87, 43, 3, 255], 93 | 'blackout':[19, 17, 15, 255], 94 | 'stone':[125, 127, 130, 255], 95 | 'gravel':[181, 182, 185, 255], 96 | 'pebble':[217, 212, 206, 255], 97 | 'sand':[185, 172, 151, 255], 98 | } 99 | 100 | 101 | LOGGING_LEVELS = { 102 | 'CRITICAL':50, 103 | 'ERROR': 40, 104 | 'WARNING':30, 105 | 'INFO': 20, 106 | 'DEBUG':10, 107 | 'NOTSET':0 108 | } 109 | 110 | 111 | # node connection property types 112 | PROPERTY_TYPES = dict( 113 | simple = ['FLOAT', 'STRING', 'BOOL', 'INT'], 114 | arrays = ['FLOAT2', 'FLOAT3', 'INT2', 'INT3', 'COLOR'], 115 | data_types = ['FILE', 'MULTI', 'MERGE', 'NODE', 'DIR'], 116 | ) 117 | 118 | 119 | # Default preferences 120 | SCENEGRAPH_PREFERENCES = { 121 | "ignore_scene_prefs": { "default": False, "desc": "Use user prefences instead of scene preferences.", "label": "Ignore scene preferences", "class": "global" }, 122 | "use_gl": {"default": False, "desc": "Render graph with OpenGL.", "label": "Use OpenGL", "class": "scene" }, 123 | "edge_type": { "default": "bezier", "desc": "Draw edges with bezier paths.", "label": "Edge style", "class": "scene" }, 124 | "render_fx": { "default": False, "desc": "Render node drop shadows and effects.", "label": "render FX", "class": "scene" }, 125 | "antialiasing": { "default": 2, "desc": "Antialiasing level.", "label": "Antialiasing", "class": "scene" }, 126 | "logging_level": { "default": 30, "desc": "Verbosity level.", "label": "Logging level", "class": "global" }, 127 | "autosave_inc": { "default": 90000, "desc": "Autosave delay (seconds x 1000).", "label": "Autosave time", "class": "global" }, 128 | "stylesheet_name": { "default": "default", "desc": "Stylesheet to use.", "label": "Stylesheet", "class": "global" }, 129 | "palette_style": { "default": "default", "desc": "Color palette to use.", "label": "Palette", "class": "global" }, 130 | "font_style": { "default": "default", "desc": "font style to use.", "label": "Font style", "class": "global" }, 131 | "viewport_mode": { "default": "smart", "desc": "viewport update mode.", "label": "Viewport Mode", "class": "global" } 132 | } 133 | 134 | 135 | 136 | SCENEGRAPH_VALID_FONTS = dict( 137 | ui = ['Arial', 'Cantarell', 'Corbel', 'DejaVu Sans', 'DejaVu Serif', 'FreeSans', 'Liberation Sans', 138 | 'Lucida Sans Unicode', 'MS Sans Serif', 'Open Sans', 'PT Sans', 'Tahoma', 'Verdana'], 139 | mono = ['Consolas', 'Courier', 'Courier 10 Pitch', 'Courier New', 'DejaVu Sans Mono', 'Fixed', 140 | 'FreeMono', 'Liberation Mono', 'Lucida Console', 'Menlo', 'Monaco'], 141 | nodes = ['Consolas', 'DejaVu Sans Mono', 'Menlo', 'DejaVu Sans'] 142 | ) 143 | 144 | 145 | EDGE_TYPES = ['bezier', 'polygon'] 146 | 147 | 148 | VIEWPORT_MODES = dict( 149 | full = 'QtGui.QGraphicsView.FullViewportUpdate', 150 | smart = 'QtGui.QGraphicsView.SmartViewportUpdate', 151 | minimal = 'QtGui.QGraphicsView.MinimalViewportUpdate', 152 | ) 153 | 154 | -------------------------------------------------------------------------------- /plugins/README: -------------------------------------------------------------------------------- 1 | # Building Custom Nodes: 2 | 3 | # Attributes are required to be named exactly as the keyword arguments passed to the constructor. 4 | 5 | #!/usr/bin/env python 6 | from SceneGraph import options 7 | from SceneGraph.core.nodes import DagNode 8 | 9 | 10 | SCENEGRAPH_NODE_TYPE = 'custom' 11 | 12 | 13 | class MyNode(DagNode): 14 | 15 | def __init__(self, *args, **kwargs): 16 | kwargs.update(node_type=SCENEGRAPH_NODE_TYPE) 17 | DagNode.__init__(self, *args, **kwargs) 18 | 19 | self.default_name = 'my_node' 20 | self.color = [172, 172, 172, 255] 21 | 22 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /plugins/asset.mtd: -------------------------------------------------------------------------------- 1 | # asset node attributes 2 | [group Asset] 3 | [input model] 4 | default NODE [FILE, "model"] 5 | label STRING "Model" 6 | desc STRING "model node input." 7 | 8 | [input lookdev] 9 | default NODE ["lookdev"] 10 | label STRING "Lookdev" 11 | desc STRING "lookdev node input." 12 | 13 | [input rig] 14 | default NODE [FILE, "rig"] 15 | label STRING "Rig" 16 | desc STRING "rigging file." 17 | 18 | [input texture] 19 | default NODE ["texture", FILE] 20 | label STRING "Textures" 21 | -------------------------------------------------------------------------------- /plugins/asset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.nodes import DagNode 3 | 4 | 5 | class AssetNode(DagNode): 6 | 7 | node_type = 'asset' 8 | node_class = 'container' 9 | node_category = 'builtin' 10 | default_name = 'asset' 11 | default_color = [174, 188, 43, 255] 12 | 13 | def __init__(self, name=None, **kwargs): 14 | DagNode.__init__(self, name, **kwargs) 15 | -------------------------------------------------------------------------------- /plugins/asset_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.ui.node_widgets import NodeWidget 3 | 4 | 5 | class AssetWidget(NodeWidget): 6 | widget_type = 'asset' 7 | node_class = 'container' 8 | def __init__(self, dagnode, parent=None): 9 | NodeWidget.__init__(self, dagnode, parent) -------------------------------------------------------------------------------- /plugins/lookdev.mtd: -------------------------------------------------------------------------------- 1 | # lookdev node attributes 2 | [group Node Attributes] 3 | [attr docstring] 4 | default STRING "Look development mapping node. Maps models to shaders." 5 | label STRING "Doc" 6 | desc STRING "node docstring." 7 | 8 | [attr force_expand] 9 | default BOOL false 10 | label STRING "Expand Node" 11 | private BOOL true 12 | 13 | [group Asset] 14 | 15 | [input model] 16 | default FILE "" 17 | label STRING "Model" 18 | desc STRING "model file." 19 | 20 | [input shader_mapping] 21 | default FILE "" 22 | label STRING "Mapping" 23 | desc STRING "shaders mapping file." 24 | 25 | [input shader] 26 | default FILE "" 27 | label STRING "Shaders" 28 | desc STRING "shaders file." 29 | 30 | -------------------------------------------------------------------------------- /plugins/lookdev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.nodes import DagNode 3 | 4 | 5 | class LookdevNode(DagNode): 6 | 7 | node_type = 'lookdev' 8 | node_class = 'container' 9 | node_category = 'builtin' 10 | default_name = 'lookdev' 11 | default_color = [170, 170, 255, 255] 12 | 13 | def __init__(self, name=None, **kwargs): 14 | DagNode.__init__(self, name, **kwargs) 15 | 16 | -------------------------------------------------------------------------------- /plugins/lookdev_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.ui.node_widgets import NodeWidget 3 | 4 | 5 | class LookdevWidget(NodeWidget): 6 | widget_type = 'lookdev' 7 | node_class = 'container' 8 | def __init__(self, dagnode, parent=None): 9 | NodeWidget.__init__(self, dagnode, parent) -------------------------------------------------------------------------------- /plugins/merge.mtd: -------------------------------------------------------------------------------- 1 | # merge node attributes 2 | [group Node Attributes] 3 | 4 | [attr force_expand] 5 | default BOOL false 6 | label STRING "Expand Node" 7 | private BOOL true 8 | 9 | [output output] 10 | default MERGE "" 11 | 12 | [group Inputs] 13 | 14 | [input inputA] 15 | default NODE "" 16 | 17 | [input inputB] 18 | default NODE "" 19 | -------------------------------------------------------------------------------- /plugins/merge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.nodes import DagNode 3 | 4 | 5 | class MergeNode(DagNode): 6 | 7 | node_type = 'merge' 8 | node_class = 'evaluate' 9 | node_category = 'builtin' 10 | default_name = 'merge' 11 | default_color = [255, 136, 136, 255] 12 | 13 | def __init__(self, name=None, **kwargs): 14 | DagNode.__init__(self, name, **kwargs) 15 | 16 | def execute(self): 17 | """ 18 | Evaluate the _command attribute. 19 | 20 | :returns: merge results. 21 | :rtype: tuple 22 | """ 23 | return (self.inputA, self.inputB) -------------------------------------------------------------------------------- /plugins/merge_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.ui.node_widgets import NodeWidget 3 | 4 | 5 | class MergeWidget(NodeWidget): 6 | widget_type = 'merge' 7 | node_class = 'container' 8 | def __init__(self, dagnode, parent=None): 9 | NodeWidget.__init__(self, dagnode, parent) 10 | 11 | -------------------------------------------------------------------------------- /plugins/model.mtd: -------------------------------------------------------------------------------- 1 | # model node attributes 2 | [group Node Attributes] 3 | 4 | [attr force_expand] 5 | default BOOL false 6 | label STRING "Expand Node" 7 | private BOOL true 8 | 9 | [input file] 10 | default FILE "" 11 | 12 | [group Outputs] 13 | [output outMesh] 14 | default NODE "" 15 | 16 | [output Alembic] 17 | default FILE "" 18 | visibility BOOL true -------------------------------------------------------------------------------- /plugins/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.nodes import DagNode 3 | 4 | 5 | class ModelNode(DagNode): 6 | 7 | node_type = 'model' 8 | node_class = 'container' 9 | node_category = 'builtin' 10 | default_name = 'model' 11 | default_color = [139, 210, 244, 255] 12 | 13 | def __init__(self, name=None, **kwargs): 14 | DagNode.__init__(self, name, **kwargs) 15 | 16 | -------------------------------------------------------------------------------- /plugins/model_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.ui.node_widgets import NodeWidget 3 | 4 | 5 | class ModelWidget(NodeWidget): 6 | widget_type = 'model' 7 | node_class = 'container' 8 | def __init__(self, dagnode, parent=None): 9 | NodeWidget.__init__(self, dagnode, parent) 10 | -------------------------------------------------------------------------------- /plugins/texture.mtd: -------------------------------------------------------------------------------- 1 | # texture node attributes 2 | [group Node Attributes] 3 | [attr force_expand] 4 | default BOOL false 5 | label STRING "Expand Node" 6 | private BOOL true 7 | 8 | [group Texture Collection] 9 | [input diffuse] 10 | label STRING "Diffuse" 11 | default DIR "" 12 | desc STRING "Diffuse texture directory." 13 | 14 | [input specular] 15 | label STRING "Specular" 16 | default DIR "" 17 | desc STRING "Specular texture directory." 18 | 19 | [input gloss] 20 | label STRING "Glossiness" 21 | default DIR "" 22 | desc STRING "Glossiness texture directory." 23 | 24 | [input bump] 25 | label STRING "Bump" 26 | default DIR "" 27 | desc STRING "Bump texture directory." 28 | 29 | [input normal] 30 | label STRING "Normal Map" 31 | default DIR "" 32 | desc STRING "Normal texture directory." 33 | 34 | [input disp] 35 | label STRING "Displacement" 36 | default DIR "" 37 | desc STRING "Displacement texture directory." 38 | -------------------------------------------------------------------------------- /plugins/texture.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.core.nodes import DagNode 3 | 4 | 5 | class TextureNode(DagNode): 6 | 7 | node_type = 'texture' 8 | node_class = 'container' 9 | node_category = 'builtin' 10 | default_name = 'texture' 11 | default_color = [111, 178, 68, 255] 12 | 13 | def __init__(self, name=None, **kwargs): 14 | DagNode.__init__(self, name, **kwargs) 15 | 16 | -------------------------------------------------------------------------------- /plugins/texture_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from SceneGraph.ui.node_widgets import NodeWidget 3 | 4 | 5 | class TextureWidget(NodeWidget): 6 | widget_type = 'texture' 7 | node_class = 'container' 8 | def __init__(self, dagnode, parent=None): 9 | NodeWidget.__init__(self, dagnode, parent) 10 | -------------------------------------------------------------------------------- /scenegraph_maya.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from PySide import QtCore, QtGui 3 | import maya.OpenMayaUI as OpenMayaUI 4 | import shiboken 5 | 6 | 7 | def getMayaWindow(): 8 | ptr = OpenMayaUI.MQtUtil.mainWindow() 9 | if ptr is not None: 10 | return shiboken.wrapInstance(long(ptr), QtGui.QMainWindow) 11 | else: 12 | print "No window found" 13 | 14 | 15 | def main(debug=False): 16 | """ 17 | Launch the Maya Scene Graph 18 | """ 19 | from SceneGraph import scenegraph 20 | 21 | global win 22 | try: 23 | win.close() 24 | except: 25 | pass 26 | 27 | win = scenegraph.SceneGraphUI(getMayaWindow(), env='maya') 28 | win.show() 29 | return win 30 | -------------------------------------------------------------------------------- /scenegraph_nuke.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from PySide import QtCore, QtGui 3 | 4 | 5 | def main(debug=False): 6 | """ 7 | Launch the Maya Scene Graph 8 | """ 9 | from SceneGraph import scenegraph 10 | 11 | global win 12 | try: 13 | win.close() 14 | except: 15 | pass 16 | 17 | win = scenegraph.SceneGraphUI(env='nuke') 18 | win.show() 19 | return win 20 | -------------------------------------------------------------------------------- /test/TestGraph.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TestGraph 4 | 5 | 6 | 7 | 0 8 | 0 9 | 742 10 | 616 11 | 12 | 13 | 14 | Test Graph 15 | 16 | 17 | 18 | 19 | 20 | 21 | Test Graph 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 95 39 | 0 40 | 41 | 42 | 43 | Add 44 | 45 | 46 | Console 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 95 55 | 0 56 | 57 | 58 | 59 | Remove 60 | 61 | 62 | Console 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 95 71 | 0 72 | 73 | 74 | 75 | Refresh 76 | 77 | 78 | Console 79 | 80 | 81 | 82 | 83 | 84 | 85 | Qt::Vertical 86 | 87 | 88 | 89 | 20 90 | 40 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 0 106 | 0 107 | 742 108 | 25 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from PySide import QtCore, QtGui 3 | from functools import partial 4 | import os 5 | import pysideuic 6 | import xml.etree.ElementTree as xml 7 | from cStringIO import StringIO 8 | from SceneGraph import core 9 | from SceneGraph import options 10 | 11 | log = core.log 12 | 13 | 14 | SCENEGRAPH_TEST_PATH = options.SCENEGRAPH_TEST_PATH 15 | SCENEGRAPH_TEST_UI = os.path.join(SCENEGRAPH_TEST_PATH, 'TestGraph.ui') 16 | 17 | 18 | def loadUiType(uiFile): 19 | """ 20 | Pyside lacks the "loadUiType" command, so we have to convert the ui file to py code in-memory first 21 | and then execute it in a special frame to retrieve the form_class. 22 | """ 23 | parsed = xml.parse(uiFile) 24 | widget_class = parsed.find('widget').get('class') 25 | form_class = parsed.find('class').text 26 | 27 | with open(uiFile, 'r') as f: 28 | o = StringIO() 29 | frame = {} 30 | 31 | pysideuic.compileUi(f, o, indent=0) 32 | pyc = compile(o.getvalue(), '', 'exec') 33 | exec pyc in frame 34 | 35 | #Fetch the base_class and form class based on their type in the xml from designer 36 | form_class = frame['Ui_%s'%form_class] 37 | base_class = eval('QtGui.%s'%widget_class) 38 | return form_class, base_class 39 | 40 | 41 | # load the ui file 42 | form_class, base_class = loadUiType(SCENEGRAPH_TEST_UI) 43 | 44 | 45 | class TestGraph(form_class, base_class): 46 | def __init__(self, parent=None, use_gl=False, debug=False, **kwargs): 47 | super(TestGraph, self).__init__(parent) 48 | 49 | self.use_gl = use_gl 50 | self._debug = debug 51 | self.environment = 'standalone' 52 | self.setupUi(self) 53 | 54 | # allow docks to be nested 55 | self.setDockNestingEnabled(True) 56 | #self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) 57 | self.setAttribute(QtCore.Qt.WA_DeleteOnClose) 58 | 59 | self.setWindowTitle("Node Test Graph") 60 | 61 | self.initializeGraphicsView() 62 | self.initializeStylesheet() 63 | self.connectSignals() 64 | 65 | def initializeStylesheet(self): 66 | """ 67 | Setup the stylehsheet. 68 | """ 69 | self.stylesheet = os.path.join(options.SCENEGRAPH_STYLESHEET_PATH, 'stylesheet.css') 70 | ssf = QtCore.QFile(self.stylesheet) 71 | ssf.open(QtCore.QFile.ReadOnly) 72 | self.setStyleSheet(str(ssf.readAll())) 73 | ssf.close() 74 | 75 | def initializeGraphicsView(self): 76 | from SceneGraph import ui 77 | self.graph = core.Graph() 78 | self.network = self.graph.network 79 | self.network.graph['environment'] = self.environment 80 | 81 | # add our custom GraphicsView object 82 | self.view = ui.GraphicsView(self.gview, ui=self, use_gl=self.use_gl, debug=self._debug) 83 | self.gviewLayout.addWidget(self.view) 84 | 85 | def connectSignals(self): 86 | self.button_add.clicked.connect(self.addAction) 87 | self.button_remove.clicked.connect(self.removeAction) 88 | self.button_refresh.clicked.connect(self.refreshAction) 89 | 90 | def addAction(self, nodetype='default'): 91 | """ 92 | Add a node to the Graph object. 93 | """ 94 | dag=self.graph.add_node(nodetype) 95 | 96 | # get the node's widget. 97 | node = dag._widget 98 | if node: 99 | #node.height=75 100 | #node.is_expanded=True 101 | return True 102 | log.warning('cannot find a node widget for "%s"' % dag.name) 103 | return False 104 | 105 | def removeAction(self): 106 | nodes = self.view.scene().selectedNodes() 107 | if nodes: 108 | for node in nodes: 109 | print node.dagnode.id 110 | return False 111 | 112 | def refreshAction(self): 113 | print '# refreshing...' 114 | return False 115 | 116 | def readGraph(self, filename): 117 | pass 118 | -------------------------------------------------------------------------------- /ui/AttributeManager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from PySide import QtCore, QtGui 4 | from functools import partial 5 | from SceneGraph import core 6 | 7 | 8 | class AttributeManager(QtGui.QMainWindow): 9 | 10 | def __init__(self, parent=None, nodes=[]): 11 | QtGui.QMainWindow.__init__(self, parent) 12 | 13 | self.centralwidget = QtGui.QWidget(self) 14 | self.centralwidget.setObjectName("centralwidget") 15 | self.mainLayout = QtGui.QVBoxLayout(self.centralwidget) 16 | self.mainLayout.setObjectName("mainLayout") 17 | self.mainGroup = QtGui.QGroupBox(self.centralwidget) 18 | self.mainGroup.setObjectName("mainGroup") 19 | 20 | self.mainGroupLayout = QtGui.QVBoxLayout(self.mainGroup) 21 | self.mainGroupLayout.setObjectName("mainGroupLayout") 22 | self.listViewLayout = QtGui.QHBoxLayout() 23 | self.listViewLayout.setObjectName("listViewLayout") 24 | 25 | self.listView = QtGui.QListView(self.mainGroup) 26 | self.listView.setObjectName("listView") 27 | self.listViewLayout.addWidget(self.listView) 28 | self.actionButtonsLayout = QtGui.QVBoxLayout() 29 | self.actionButtonsLayout.setObjectName("actionButtonsLayout") 30 | 31 | self.tb_new = QtGui.QToolButton(self.mainGroup) 32 | self.tb_new.setObjectName("tb_new") 33 | self.tb_new.setProperty("class", "Prefs") 34 | self.actionButtonsLayout.addWidget(self.tb_new) 35 | self.tb_new.setMinimumSize(70, 20) 36 | 37 | self.tb_delete = QtGui.QToolButton(self.mainGroup) 38 | self.tb_delete.setObjectName("tb_delete") 39 | self.tb_delete.setProperty("class", "Prefs") 40 | self.actionButtonsLayout.addWidget(self.tb_delete) 41 | self.tb_delete.setMinimumSize(70, 20) 42 | 43 | spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) 44 | self.actionButtonsLayout.addItem(spacerItem) 45 | self.listViewLayout.addLayout(self.actionButtonsLayout) 46 | self.mainGroupLayout.addLayout(self.listViewLayout) 47 | self.detailGroup = QtGui.QGroupBox(self.mainGroup) 48 | self.detailGroup.setObjectName("detailGroup") 49 | self.detailGroupLayout = QtGui.QGridLayout(self.detailGroup) 50 | self.detailGroupLayout.setObjectName("detailGroupLayout") 51 | self.label_name = QtGui.QLabel(self.detailGroup) 52 | self.label_name.setObjectName("label_name") 53 | self.detailGroupLayout.addWidget(self.label_name, 0, 0, 1, 1) 54 | self.line_name = QtGui.QLineEdit(self.detailGroup) 55 | self.line_name.setObjectName("line_name") 56 | self.detailGroupLayout.addWidget(self.line_name, 0, 1, 1, 2) 57 | self.label_nice_name = QtGui.QLabel(self.detailGroup) 58 | self.label_nice_name.setObjectName("label_nice_name") 59 | self.detailGroupLayout.addWidget(self.label_nice_name, 1, 0, 1, 1) 60 | self.line_nice_name = QtGui.QLineEdit(self.detailGroup) 61 | self.line_nice_name.setObjectName("line_nice_name") 62 | self.detailGroupLayout.addWidget(self.line_nice_name, 1, 1, 1, 2) 63 | self.label_type = QtGui.QLabel(self.detailGroup) 64 | self.label_type.setObjectName("label_type") 65 | self.detailGroupLayout.addWidget(self.label_type, 2, 0, 1, 1) 66 | self.rb_connectable = QtGui.QRadioButton(self.detailGroup) 67 | self.rb_connectable.setObjectName("rb_connectable") 68 | self.detailGroupLayout.addWidget(self.rb_connectable, 3, 1, 1, 1) 69 | self.rb_private = QtGui.QRadioButton(self.detailGroup) 70 | self.rb_private.setObjectName("rb_private") 71 | self.detailGroupLayout.addWidget(self.rb_private, 3, 2, 1, 1) 72 | self.menu_type = QtGui.QComboBox(self.detailGroup) 73 | self.menu_type.setObjectName("menu_type") 74 | self.detailGroupLayout.addWidget(self.menu_type, 2, 1, 1, 2) 75 | self.mainGroupLayout.addWidget(self.detailGroup) 76 | self.mainLayout.addWidget(self.mainGroup) 77 | self.buttonsLayout = QtGui.QHBoxLayout() 78 | self.buttonsLayout.setObjectName("buttonsLayout") 79 | 80 | spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 81 | self.buttonsLayout.addItem(spacerItem1) 82 | self.button_cancel = QtGui.QPushButton(self.centralwidget) 83 | self.button_cancel.setObjectName("button_cancel") 84 | self.buttonsLayout.addWidget(self.button_cancel) 85 | self.button_accept = QtGui.QPushButton(self.centralwidget) 86 | self.button_accept.setObjectName("button_accept") 87 | self.buttonsLayout.addWidget(self.button_accept) 88 | self.mainLayout.addLayout(self.buttonsLayout) 89 | self.setCentralWidget(self.centralwidget) 90 | self.menubar = QtGui.QMenuBar(self) 91 | self.menubar.setGeometry(QtCore.QRect(0, 0, 474, 25)) 92 | self.menubar.setObjectName("menubar") 93 | self.setMenuBar(self.menubar) 94 | self.statusbar = QtGui.QStatusBar(self) 95 | self.statusbar.setObjectName("statusbar") 96 | self.setStatusBar(self.statusbar) 97 | 98 | self.model = AttributesListModel() 99 | self.listView.setModel(self.model) 100 | self.selection_model = self.listView.selectionModel() 101 | 102 | self.listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 103 | #self.listView.customContextMenuRequested.connect(self.listContextMenu) 104 | 105 | self.initializeUI() 106 | self.connectSignals() 107 | 108 | def initializeUI(self): 109 | """ 110 | Set up the main UI 111 | """ 112 | self.setWindowTitle("Attributes Manager") 113 | self.mainGroup.setTitle("Attributes") 114 | self.tb_new.setText("New...") 115 | self.tb_delete.setText("Delete...") 116 | self.detailGroup.setTitle("Detail") 117 | self.label_name.setText("Name:") 118 | self.label_nice_name.setText("Nice Name:") 119 | self.label_type.setText("Type:") 120 | self.rb_connectable.setText("Connectable") 121 | self.rb_private.setText("Private") 122 | self.button_cancel.setText("&Cancel") 123 | self.button_accept.setText("&OK") 124 | 125 | def connectSignals(self): 126 | pass 127 | 128 | 129 | 130 | 131 | class AttributesListModel(QtCore.QAbstractListModel): 132 | def __init__(self, parent=None, nodes=[],): 133 | QtCore.QAbstractListModel.__init__(self, parent) 134 | 135 | self.nodes = nodes 136 | self.attributes = [] 137 | 138 | def addAttributes(self, attributes): 139 | """ 140 | adds a list of tuples to the assets value 141 | """ 142 | self.insertRows(0, len(attributes), values=attributes) 143 | 144 | def getAttributes(self): 145 | return self.attributes 146 | 147 | def rowCount(self, parent=QtCore.QModelIndex()): 148 | return len(self.attributes) 149 | 150 | def data(self, index, role): 151 | row = index.row() 152 | column = index.column() 153 | bookmark = self.attributes[row] 154 | 155 | if role == QtCore.Qt.DecorationRole: 156 | icon = self.icons.get(bookmark.icon) 157 | return icon 158 | 159 | if role == QtCore.Qt.DisplayRole: 160 | return bookmark.name 161 | 162 | if role == QtCore.Qt.ToolTipRole: 163 | return bookmark.path 164 | 165 | def flags(self, index): 166 | return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable 167 | 168 | def setData(self, index, value, role=QtCore.Qt.EditRole): 169 | if role == QtCore.Qt.EditRole: 170 | row = index.row() 171 | self.dataChanged.emit(index, index) 172 | return True 173 | return False 174 | 175 | def insertRows(self, position, rows, parent=QtCore.QModelIndex(), values=[]): 176 | self.beginInsertRows(parent, position, position + rows - 1) 177 | for row in range(rows): 178 | self.attributes.insert(position + row, values[row]) 179 | self.endInsertRows() 180 | return True 181 | 182 | def removeRows(self, position, rows, parent=QtCore.QModelIndex()): 183 | self.beginRemoveRows(parent, position, position + rows - 1) 184 | self.attributes = (self.attributes[:position] + self.attributes[position + rows:]) 185 | self.endRemoveRows() 186 | return True -------------------------------------------------------------------------------- /ui/AttributeManager.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 474 10 | 468 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | Attributes 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 60 36 | 0 37 | 38 | 39 | 40 | New... 41 | 42 | 43 | Prefs 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 60 52 | 0 53 | 54 | 55 | 56 | Delete 57 | 58 | 59 | Prefs 60 | 61 | 62 | 63 | 64 | 65 | 66 | Qt::Vertical 67 | 68 | 69 | 70 | 20 71 | 40 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Detail 84 | 85 | 86 | 87 | 88 | 89 | Name: 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Nice Name: 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Type: 110 | 111 | 112 | 113 | 114 | 115 | 116 | Connectable 117 | 118 | 119 | 120 | 121 | 122 | 123 | Private 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Qt::Horizontal 142 | 143 | 144 | 145 | 40 146 | 20 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | &Cancel 155 | 156 | 157 | 158 | 159 | 160 | 161 | &OK 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 0 173 | 0 174 | 474 175 | 25 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /ui/GraphAttributes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from PySide import QtCore, QtGui 3 | from SceneGraph import core 4 | from SceneGraph import util 5 | 6 | 7 | class GraphAttributes(QtGui.QDialog): 8 | 9 | def __init__(self, parent=None): 10 | QtGui.QDialog.__init__(self, parent) 11 | self.setAttribute(QtCore.Qt.WA_DeleteOnClose) 12 | 13 | verticalLayout = QtGui.QVBoxLayout(self) 14 | self.setLayout(verticalLayout) 15 | 16 | self.groupBox = QtGui.QGroupBox(self) 17 | self.groupBox.setObjectName("groupBox") 18 | self.gridLayout = QtGui.QGridLayout(self.groupBox) 19 | self.gridLayout.setObjectName("gridLayout") 20 | self.desc_label = QtGui.QLabel(self.groupBox) 21 | self.desc_label.setObjectName("desc_label") 22 | self.gridLayout.addWidget(self.desc_label, 0, 0, 1, 2) 23 | 24 | self.atttr_name_label = QtGui.QLabel(self.groupBox) 25 | self.atttr_name_label.setObjectName("atttr_name_label") 26 | self.gridLayout.addWidget(self.atttr_name_label, 1, 0, 1, 1) 27 | self.attr_name_edit = QtGui.QLineEdit(self.groupBox) 28 | self.attr_name_edit.setObjectName("attr_name_edit") 29 | self.gridLayout.addWidget(self.attr_name_edit, 1, 1, 1, 1) 30 | 31 | self.atttr_value_label = QtGui.QLabel(self.groupBox) 32 | self.atttr_value_label.setObjectName("atttr_value_label") 33 | self.gridLayout.addWidget(self.atttr_value_label, 2, 0, 1, 1) 34 | self.attr_value_edit = QtGui.QLineEdit(self.groupBox) 35 | self.attr_value_edit.setObjectName("attr_value_edit") 36 | self.gridLayout.addWidget(self.attr_value_edit, 2, 1, 1, 1) 37 | 38 | self.type_label = QtGui.QLabel(self.groupBox) 39 | self.type_label.setObjectName("type_label") 40 | self.gridLayout.addWidget(self.type_label, 3, 1, 1, 1) 41 | 42 | self.type_menu = QtGui.QComboBox(self.groupBox) 43 | self.type_menu.setObjectName("type_menu") 44 | self.gridLayout.addWidget(self.type_menu, 3, 1, 1, 1) 45 | 46 | self.dagnodes_rb = QtGui.QRadioButton(self.groupBox) 47 | self.dagnodes_rb.setObjectName("dagnodes_rb") 48 | self.gridLayout.addWidget(self.dagnodes_rb, 4, 1, 1, 1) 49 | verticalLayout.addWidget(self.groupBox) 50 | 51 | self.buttonBox = QtGui.QDialogButtonBox(self) 52 | self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) 53 | self.buttonBox.setObjectName("buttonBox") 54 | verticalLayout.addWidget(self.buttonBox) 55 | 56 | self.initializeUI() 57 | 58 | # signals 59 | self.buttonBox.accepted.connect(self.acceptedAction) 60 | self.buttonBox.rejected.connect(self.rejectedAction) 61 | 62 | def initializeUI(self): 63 | self.groupBox.setTitle("Update Attributes") 64 | self.desc_label.setText("Pass attributes directly into the graph.") 65 | self.type_label.setText("Type:") 66 | self.atttr_name_label.setText("Attribute:") 67 | self.atttr_value_label.setText("Value:") 68 | self.dagnodes_rb.setText("dag nodes") 69 | 70 | def sizeHint(self): 71 | return QtCore.QSize(294, 212) 72 | 73 | def acceptedAction(self): 74 | attr_name = self.attr_name_edit.text() 75 | attr_val = self.attr_value_edit.text() 76 | 77 | if not attr_name or not attr_val: 78 | return 79 | 80 | value_type = util.attr_type(attr_val) 81 | attr_val = util.auto_convert(attr_val) 82 | 83 | self.parent().handler.scene.updateNodes(**{attr_name:attr_val}) 84 | self.close() 85 | 86 | def rejectedAction(self): 87 | self.close() 88 | -------------------------------------------------------------------------------- /ui/PaletteEditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 363 10 | 363 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | Palette Colors 22 | 23 | 24 | 25 | 26 | 27 | 28 | 60 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 60 42 | 0 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 55 | 0 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | @color-attr 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | @color-attr 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 60 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | @color-attr 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | @color-attr 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Qt::Vertical 120 | 121 | 122 | 123 | 20 124 | 40 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Qt::Horizontal 138 | 139 | 140 | 141 | 40 142 | 20 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | &Cancel 151 | 152 | 153 | 154 | 155 | 156 | 157 | &Save 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 0 169 | 0 170 | 363 171 | 25 172 | 173 | 174 | 175 | 176 | File 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Open 185 | 186 | 187 | 188 | 189 | Save 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /ui/PluginManager.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PluginManager 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 500 11 | 12 | 13 | 14 | PluginManager 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | Plugins 25 | 26 | 27 | 28 | 4 29 | 30 | 31 | 4 32 | 33 | 34 | 35 | 36 | Loaded Plugins 37 | 38 | 39 | 40 | 6 41 | 42 | 43 | 9 44 | 45 | 46 | 47 | 48 | 9 49 | 50 | 51 | 9 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 4 65 | 66 | 67 | 68 | 69 | 70 | 75 71 | 0 72 | 73 | 74 | 75 | Disable 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 75 84 | 0 85 | 86 | 87 | 88 | Reload 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 75 97 | 0 98 | 99 | 100 | 101 | Load... 102 | 103 | 104 | 105 | 106 | 107 | 108 | Qt::Vertical 109 | 110 | 111 | 112 | 20 113 | 40 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Qt::Horizontal 131 | 132 | 133 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | buttonBox 143 | accepted() 144 | PluginManager 145 | accept() 146 | 147 | 148 | 248 149 | 254 150 | 151 | 152 | 157 153 | 274 154 | 155 | 156 | 157 | 158 | buttonBox 159 | rejected() 160 | PluginManager 161 | reject() 162 | 163 | 164 | 316 165 | 260 166 | 167 | 168 | 286 169 | 274 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /ui/commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from PySide import QtGui 4 | 5 | 6 | class SceneNodesCommand(QtGui.QUndoCommand): 7 | def __init__(self, old, new, scene, msg=None, parent=None): 8 | QtGui.QUndoCommand.__init__(self, parent) 9 | 10 | self.restored = True 11 | self.scene = scene 12 | 13 | self.data_old = old 14 | self.data_new = new 15 | 16 | self.diff = DictDiffer(old, new) 17 | self.setText(self.diff.output()) 18 | 19 | # set the current undo view message 20 | if msg is not None: 21 | self.setText(msg) 22 | 23 | def id(self): 24 | return (0xAC00 + 0x0002) 25 | 26 | def undo(self): 27 | self.scene.restoreNodes(self.data_old) 28 | 29 | def redo(self): 30 | if not self.restored: 31 | self.scene.restoreNodes(self.data_new) 32 | self.restored = False 33 | 34 | 35 | class SceneChangedCommand(QtGui.QUndoCommand): 36 | """ 37 | Command to track scene changes. 38 | """ 39 | def __init__(self, old, new, scene, msg=None, parent=None): 40 | QtGui.QUndoCommand.__init__(self, parent) 41 | 42 | self.restored = True 43 | self.scene = scene 44 | 45 | self.data_old = old 46 | self.data_new = new 47 | 48 | self.diff = DictDiffer(old, new) 49 | self.setText(self.diff.output()) 50 | 51 | # set the current undo view message 52 | if msg is not None: 53 | self.setText(msg) 54 | 55 | def id(self): 56 | return (0xAC00 + 0x0003) 57 | 58 | def undo(self): 59 | self.scene.restoreNodes(self.data_old) 60 | 61 | def redo(self): 62 | if not self.restored: 63 | self.scene.restoreNodes(self.data_new) 64 | self.restored = False 65 | 66 | 67 | class DictDiffer(object): 68 | """ 69 | Calculate the difference between two dictionaries as: 70 | (1) items added 71 | (2) items removed 72 | (3) keys same in both but changed values 73 | (4) keys same in both and unchanged values 74 | """ 75 | def __init__(self, current_dict, past_dict): 76 | self.current_dict, self.past_dict = current_dict, past_dict 77 | self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys()) 78 | self.intersect = self.set_current.intersection(self.set_past) 79 | 80 | def added(self): 81 | return self.set_current - self.intersect 82 | 83 | def removed(self): 84 | return self.set_past - self.intersect 85 | 86 | def changed(self): 87 | return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o]) 88 | 89 | def unchanged(self): 90 | return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o]) 91 | 92 | def output(self): 93 | """ 94 | Return a string for the undo command view. 95 | """ 96 | msg = "" 97 | if self.changed(): 98 | for x in self.changed(): 99 | msg += "%s," % x 100 | msg = re.sub(",$", "", msg) 101 | msg+=" changed" 102 | 103 | return msg -------------------------------------------------------------------------------- /ui/designer/AddAttributeDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AddAttributeDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 316 10 | 259 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | Create Attribute 21 | 22 | 23 | 24 | QFormLayout::AllNonFixedFieldsGrow 25 | 26 | 27 | Qt::AlignCenter 28 | 29 | 30 | 31 | 32 | 33 | 110 34 | 0 35 | 36 | 37 | 38 | Name: 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 110 50 | 0 51 | 52 | 53 | 54 | ID: 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 110 66 | 0 67 | 68 | 69 | 70 | Type: 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Connectable 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 110 89 | 0 90 | 91 | 92 | 93 | Connection type: 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | Qt::Vertical 107 | 108 | 109 | 110 | 20 111 | 40 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Qt::Horizontal 120 | 121 | 122 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | buttonBox 132 | accepted() 133 | AddAttributeDialog 134 | accept() 135 | 136 | 137 | 248 138 | 254 139 | 140 | 141 | 157 142 | 274 143 | 144 | 145 | 146 | 147 | buttonBox 148 | rejected() 149 | AddAttributeDialog 150 | reject() 151 | 152 | 153 | 316 154 | 260 155 | 156 | 157 | 286 158 | 274 159 | 160 | 161 | 162 | 163 | checkbox_connectable 164 | toggled(bool) 165 | connectionTypeMenu 166 | setVisible(bool) 167 | 168 | 169 | 259 170 | 134 171 | 172 | 173 | 255 174 | 160 175 | 176 | 177 | 178 | 179 | checkbox_connectable 180 | toggled(bool) 181 | connectionTypeLabel 182 | setVisible(bool) 183 | 184 | 185 | 259 186 | 134 187 | 188 | 189 | 81 190 | 159 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /ui/designer/AttributeEditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 303 10 | 462 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 3 19 | 20 | 21 | 3 22 | 23 | 24 | 25 | 26 | Node: 27 | 28 | 29 | 30 | 3 31 | 32 | 33 | 3 34 | 35 | 36 | 37 | 38 | Transform 39 | 40 | 41 | true 42 | 43 | 44 | 45 | 3 46 | 47 | 48 | 3 49 | 50 | 51 | 3 52 | 53 | 54 | 10 55 | 56 | 57 | 3 58 | 59 | 60 | 3 61 | 62 | 63 | 64 | 65 | Pos: 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 3 74 | 75 | 76 | 0 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Width: 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Color 104 | 105 | 106 | true 107 | 108 | 109 | 110 | QFormLayout::AllNonFixedFieldsGrow 111 | 112 | 113 | 3 114 | 115 | 116 | 3 117 | 118 | 119 | 3 120 | 121 | 122 | 10 123 | 124 | 125 | 3 126 | 127 | 128 | 3 129 | 130 | 131 | 132 | 133 | Pos: 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 3 142 | 143 | 144 | 0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Width: 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Qt::Vertical 172 | 173 | 174 | 175 | 20 176 | 40 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /ui/designer/Console.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 321 10 | 280 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | QFormLayout::AllNonFixedFieldsGrow 19 | 20 | 21 | 22 | 23 | View Rect: 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Scene Rect: 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Zoom Level: 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Cursor X: 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Cursor Y: 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /ui/designer/GraphAttributes.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | GraphAttributes 4 | 5 | 6 | 7 | 0 8 | 0 9 | 278 10 | 245 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | Update Attributes 21 | 22 | 23 | 24 | 25 | 26 | Pass attributes directly into the graph. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | dag nodes 40 | 41 | 42 | 43 | 44 | 45 | 46 | Value: 47 | 48 | 49 | 50 | 51 | 52 | 53 | Attribute: 54 | 55 | 56 | 57 | 58 | 59 | 60 | Type: 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /ui/designer/Preferences.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Preferences 4 | 5 | 6 | 7 | 0 8 | 0 9 | 438 10 | 234 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | SceneGraph Preferences 21 | 22 | 23 | 24 | 25 | 26 | Performance 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | use OpenGL 36 | 37 | 38 | 39 | 40 | 41 | 42 | render effects 43 | 44 | 45 | 46 | 47 | 48 | 49 | bezier lines 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Vertical 60 | 61 | 62 | 63 | 20 64 | 76 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Qt::Horizontal 76 | 77 | 78 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | buttonBox 88 | accepted() 89 | Preferences 90 | accept() 91 | 92 | 93 | 248 94 | 254 95 | 96 | 97 | 157 98 | 274 99 | 100 | 101 | 102 | 103 | buttonBox 104 | rejected() 105 | Preferences 106 | reject() 107 | 108 | 109 | 316 110 | 260 111 | 112 | 113 | 286 114 | 274 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /ui/designer/widgets/DockWidget_stylesheet.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 687 10 | 526 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | QWidget{ 18 | color: #b1b1b1; 19 | background-color: #323232; 20 | font-size: 8pt; 21 | font-family: "SansSerif"; 22 | } 23 | 24 | QDockWidget::title{ 25 | text-align: left; 26 | border-bottom: 1px solid #252525; 27 | border-right: 1px solid #252525; 28 | border-top: 1px solid #616161; 29 | border-left: 1px solid #616161; 30 | spacing: 3px; /* spacing between items in the tool bar */ 31 | background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #616161, stop:1 #323232); 32 | padding-left: 12px; 33 | border-radius: 4px; 34 | } 35 | 36 | QDockWidget::close-button, QDockWidget::float-button { 37 | border: 1px inset #323232; 38 | background: #4b4b4b; 39 | border-radius: 1px; 40 | padding: -3px, 3px, 3px, 3px; 41 | margin: 3px, 3px, 3px, 3px; 42 | } 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Tab 1 52 | 53 | 54 | 55 | 56 | Tab 2 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 0 68 | 687 69 | 18 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 250 78 | 36 79 | 80 | 81 | 82 | Attribute Editor 83 | 84 | 85 | 1 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /ui/designer/widgets/FileEditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 366 10 | 49 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 3 19 | 20 | 21 | 3 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ... 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ui/designer/widgets/Slider.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 420 10 | 101 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | QWidget{ 18 | color: #b1b1b1; 19 | background-color: #323232; 20 | font-size: 8pt; 21 | font-family: "SansSerif"; 22 | } 23 | 24 | QSlider::groove:horizontal { 25 | border: 1px inset #1C1C1C; 26 | height: 6px; 27 | border-radius: 3px; 28 | } 29 | 30 | QSlider::sub-page:horizontal { 31 | background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #373737, stop: 1 #4A4A4A); 32 | border: 1px inset #1C1C1C; 33 | border-radius: 3px; 34 | } 35 | 36 | /* groove background on right of slider */ 37 | QSlider::add-page:horizontal { 38 | background: #7D7D7D; 39 | border: 1px inset #1C1C1C; 40 | border-radius: 3px; 41 | } 42 | 43 | QSlider::handle:horizontal { 44 | background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #6C6C6C, stop:1 #9D9D9D); 45 | border: 1px solid #373737; 46 | width: 6px; 47 | height: 10px; 48 | margin-top: -4px; 49 | margin-bottom: -4px; 50 | border-radius: 2px; 51 | } 52 | 53 | 54 | 55 | 56 | 57 | Slider: 58 | 59 | 60 | 61 | 62 | 63 | 64 | false 65 | 66 | 67 | 50 68 | 69 | 70 | Qt::Horizontal 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ui/handlers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from PySide import QtCore 3 | 4 | from SceneGraph import core 5 | from SceneGraph.ui import commands 6 | 7 | 8 | log = core.log 9 | 10 | 11 | class SceneEventHandler(QtCore.QObject): 12 | def __init__(self, parent=None): 13 | QtCore.QObject.__init__(self, parent) 14 | 15 | self.ui = None # reference to the parent MainWindow 16 | self.graph = None # reference to the Graph instance 17 | self._initialized = False # indicates the current scene has been read & built 18 | 19 | if parent is not None: 20 | self.ui = parent.ui 21 | 22 | # connections to parent menuitems 23 | self.ui.action_evaluate.triggered.connect(self.evaluate) 24 | self.ui.action_update_graph.triggered.connect(self.graphUpdated) 25 | 26 | if not self.connectGraph(parent): 27 | log.error('cannot connect SceneEventHandler to Graph.') 28 | 29 | def updateStatus(self, msg, level='info'): 30 | self.ui.updateStatus(msg, level=level) 31 | 32 | @property 33 | def scene(self): 34 | """ 35 | Returns the parent GraphicsScene. 36 | 37 | :returns: parent GraphicsScene 38 | :rtype: `GraphicsScene` 39 | """ 40 | return self.parent() 41 | 42 | @property 43 | def view(self): 44 | """ 45 | Returns the connected GraphicsView. 46 | 47 | :returns: connected GraphicsView 48 | :rtype: `GraphicsView` 49 | """ 50 | return self.ui.view 51 | 52 | @property 53 | def undo_stack(self): 54 | return self.ui.undo_stack 55 | 56 | def connectGraph(self, scene): 57 | """ 58 | Connect the parent scenes' Graph object. 59 | 60 | :param GraphicsScene scene: current QGraphicsScene scene. 61 | 62 | :returns: connection was successful. 63 | :rtype: bool 64 | """ 65 | if hasattr(scene, 'graph'): 66 | graph = scene.graph 67 | if graph.mode == 'standalone': 68 | self.graph = graph 69 | self.graph.handler = self 70 | 71 | # connect graph signals 72 | self.graph.nodesAdded += self.nodesAddedEvent 73 | self.graph.edgesAdded += self.edgesAddedEvent 74 | self.graph.graphUpdated += self.graphUpdated 75 | self.graph.graphAboutToBeSaved += self.graphAboutToBeSaved 76 | self.graph.graphRefreshed += self.graphAboutToBeSaved 77 | 78 | self.graph.graphRead += self.graphReadEvent 79 | 80 | self.graph.mode = 'ui' 81 | log.info('SceneHandler: connecting Graph...') 82 | 83 | # load the node widgets from disk 84 | #self.graph.plug_mgr.load_widgets() 85 | 86 | # start the autosave timer for 2min (120s x 1000) 87 | self.ui.autosave_timer.start(30000) 88 | return True 89 | return False 90 | 91 | def resetScene(self): 92 | """ 93 | .. note:: 94 | - compatibility 95 | - deprecated 96 | """ 97 | self.scene.clear() 98 | 99 | def graphReadEvent(self, graph, **kwargs): 100 | """ 101 | Update the scene and ui with preferences read from a scene. 102 | 103 | :param Graph graph: Graph instance. 104 | """ 105 | self._initialized = True 106 | 107 | if not self.ui.ignore_scene_prefs: 108 | self.ui.initializePreferencesPane(**kwargs) 109 | self.scene.updateScenePreferences(**kwargs) 110 | for attr, value in kwargs.iteritems(): 111 | if hasattr(self.ui, attr): 112 | setattr(self.ui, attr, value) 113 | 114 | def restoreGeometry(self, **kwargs): 115 | """ 116 | Retore scene geometry. 117 | """ 118 | pos = kwargs.pop('pos', (0.0, 0.0)) 119 | scale = kwargs.pop('scale', (1.0, 1.0)) 120 | 121 | self.view.resetTransform() 122 | self.view.centerOn(*pos) 123 | self.view.scale(*scale) 124 | 125 | #- Events ---- 126 | 127 | def evaluate(self): 128 | return True 129 | 130 | def nodesAddedEvent(self, graph, ids): 131 | """ 132 | Callback method. 133 | 134 | :param list ids: DagNode ids. 135 | """ 136 | old_snapshot = self.graph.snapshot() 137 | self.scene.addNodes(ids) 138 | # push a snapshot to the undo stack 139 | new_snapshot = self.graph.snapshot() 140 | self.undo_stack.push(commands.SceneNodesCommand(old_snapshot, new_snapshot, self.scene, msg='nodes added')) 141 | 142 | def edgesAddedEvent(self, graph, edges): 143 | """ 144 | Callback method. 145 | """ 146 | old_snapshot = self.graph.snapshot() 147 | self.scene.addEdges(edges) 148 | 149 | # push a snapshot to the undo stack 150 | new_snapshot = self.graph.snapshot() 151 | self.undo_stack.push(commands.SceneNodesCommand(old_snapshot, new_snapshot, self.scene, msg='edges added')) 152 | 153 | def removeSceneNodes(self, nodes): 154 | """ 155 | Signal Graph when the scene is updated. 156 | 157 | .. todo:: 158 | - not currently used. 159 | 160 | :param list nodes: list of widgets. 161 | """ 162 | old_snapshot = self.graph.snapshot() 163 | if not nodes: 164 | log.error('no nodes specified.') 165 | return False 166 | 167 | #print '# DEBUG: removing scene nodes...' 168 | for node in nodes: 169 | if self.scene: 170 | if self.scene.is_node(node): 171 | dag = node.dagnode 172 | nid = dag.id 173 | if self.graph.remove_node(nid): 174 | log.debug('removing dag node: %s' % nid) 175 | node.close() 176 | 177 | if self.scene.is_edge(node): 178 | if node.ids in self.graph.network.edges(): 179 | log.debug('removing edge: %s' % str(node.ids)) 180 | self.graph.remove_edge(*node.ids) 181 | node.close() 182 | 183 | # push a snapshot to the undo stack 184 | new_snapshot = self.graph.snapshot() 185 | self.undo_stack.push(commands.SceneNodesCommand(old_snapshot, new_snapshot, self.scene, msg='nodes deleted')) 186 | 187 | def getInterfacePreferences(self): 188 | """ 189 | Get interface preferences from the QSettings. 190 | 191 | :returns: ui attributes. 192 | :rtype: dict 193 | """ 194 | result = dict() 195 | for attr in self.ui.qsettings.prefs_keys(): 196 | if hasattr(self.ui, attr): 197 | value = getattr(self.ui, attr) 198 | result[str(attr)] = value 199 | return result 200 | 201 | def graphAboutToBeSaved(self, graph): 202 | """ 203 | Update the graph preferences attribute as the scene is about to be saved. 204 | 205 | :returns: interface preferences. 206 | :rtype: dict 207 | """ 208 | result = self.getInterfacePreferences() 209 | self.graph.updateGraphPreferences(**result) 210 | 211 | def graphUpdated(self, *args): 212 | """ 213 | Signal when the graph updates. 214 | """ 215 | widgets_to_remove = [] 216 | for id in self.scene.scenenodes: 217 | widget = self.scene.scenenodes.get(id) 218 | if self.scene.is_node(widget): 219 | if widget.id not in self.graph.network.nodes(): 220 | widgets_to_remove.append(widget) 221 | 222 | if self.scene.is_edge(widget): 223 | if widget.ids not in self.graph.network.edges(): 224 | widgets_to_remove.append(widget) 225 | 226 | for node in widgets_to_remove: 227 | if self.scene.is_edge(node): 228 | src_conn = node.source_item 229 | dest_conn = node.dest_item 230 | print '# DEBUG: removing edge: ', node 231 | node.close() 232 | 233 | if self.scene.is_node(node): 234 | print '# DEBUG: removing node: ', node 235 | node.close() 236 | 237 | def dagNodesUpdatedEvent(self, dagnodes): 238 | """ 239 | Update dag nodes from an external UI. 240 | """ 241 | if dagnodes: 242 | print '# DEBUG: dag nodes updated: ', [node.name for node in dagnodes] -------------------------------------------------------------------------------- /ui/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from PySide import QtCore, QtGui 4 | 5 | from SceneGraph import options 6 | from SceneGraph.core import log 7 | from SceneGraph.ui import stylesheet 8 | 9 | 10 | class Settings(QtCore.QSettings): 11 | 12 | def __init__(self, filename, frmt=QtCore.QSettings.IniFormat, parent=None, max_files=10): 13 | QtCore.QSettings.__init__(self, filename, frmt, parent) 14 | 15 | 16 | self._max_files = max_files 17 | self._parent = parent 18 | self._stylesheet = stylesheet.StylesheetManager(parent) 19 | parent.stylesheet = self._stylesheet 20 | self._groups = ['MainWindow', 'RecentFiles', 'Preferences'] 21 | self.initialize() 22 | 23 | def initialize(self): 24 | """ 25 | Setup the file for the first time. 26 | """ 27 | if 'MainWindow' not in self.childGroups(): 28 | if self._parent is not None: 29 | self.setValue('MainWindow/geometry/default', self._parent.saveGeometry()) 30 | self.setValue('MainWindow/windowState/default', self._parent.saveState()) 31 | 32 | if 'RecentFiles' not in self.childGroups(): 33 | self.beginWriteArray('RecentFiles', 0) 34 | self.endArray() 35 | 36 | while self.group(): 37 | #print '# DEBUG: Settings: current group: "%s"' % self.group() 38 | self.endGroup() 39 | self.initializePreferences() 40 | 41 | def initializePreferences(self): 42 | """ 43 | Set up the default preferences from global options. 44 | """ 45 | if 'Preferences' not in self.childGroups(): 46 | self.beginGroup("Preferences") 47 | # query the defauls from options. 48 | for option in options.SCENEGRAPH_PREFERENCES: 49 | if 'default' in options.SCENEGRAPH_PREFERENCES.get(option): 50 | def_value = options.SCENEGRAPH_PREFERENCES.get(option).get('default') 51 | if type(def_value) is bool: 52 | def_value = int(def_value) 53 | self.setValue('default/%s' % option, def_value) 54 | 55 | # font defaults 56 | for font_attr, font_value in self._stylesheet.font_defaults().iteritems(): 57 | if not ':' in font_attr: 58 | self.setValue('default/%s' % font_attr, font_value) 59 | 60 | while self.group(): 61 | #print '# DEBUG: Settings: current group: "%s"' % self.group() 62 | self.endGroup() 63 | 64 | @property 65 | def groups(self): 66 | """ 67 | Returns the current preferences groups. 68 | 69 | :returns: current preferences groups. 70 | :rtype: list 71 | """ 72 | return self._groups 73 | 74 | def addGroup(self, group): 75 | """ 76 | Add a group to the current preferences groups. 77 | 78 | :param str group: name of group to add. 79 | 80 | :returns: group was successfully added. 81 | :rtype: bool 82 | """ 83 | if group not in self._groups: 84 | self._groups.append(group) 85 | return True 86 | return False 87 | 88 | def removeGroup(self, group): 89 | """ 90 | Remove a group from the preferences groups. 91 | 92 | :param str group: name of group to remove. 93 | 94 | :returns: group was successfully removed. 95 | :rtype: bool 96 | """ 97 | if group in self._groups: 98 | self._groups.remove(group) 99 | return True 100 | return False 101 | 102 | def window_keys(self): 103 | """ 104 | Resturns a list of all window keys to save for layouts, or rebuilding last 105 | layout on launch. 106 | 107 | :returns: list of keys to query/save. 108 | :rtype: list 109 | """ 110 | if self._parent is None: 111 | return [] 112 | result = ['MainWindow'] 113 | for dock in self._parent.findChildren(QtGui.QDockWidget): 114 | dock_name = dock.objectName() 115 | result.append(str(dock_name)) 116 | return result 117 | 118 | def prefs_keys(self): 119 | """ 120 | Returns a list of preferences keys. 121 | 122 | :returns: list of user prefs keys. 123 | :rtype: list 124 | """ 125 | results = [] 126 | self.beginGroup('Preferences') 127 | results = self.childKeys() 128 | self.endGroup() 129 | return results 130 | 131 | def get_layouts(self): 132 | """ 133 | Returns a list of window layout names. 134 | 135 | :returns: list of window layout names. 136 | :rtype: list 137 | """ 138 | layout_names = [] 139 | layout_keys = ['%s/geometry' % x for x in self.window_keys()] 140 | 141 | 142 | for k in self.allKeys(): 143 | if 'geometry' in k: 144 | attrs = k.split('/geometry/') 145 | if len(attrs) > 1: 146 | layout_names.append(str(attrs[-1])) 147 | 148 | return sorted(list(set(layout_names))) 149 | 150 | #- Layouts ---- 151 | 152 | def saveLayout(self, layout): 153 | """ 154 | Save a named layout. 155 | 156 | :param str layout: layout name to save. 157 | """ 158 | log.info('saving layout: "%s"' % layout) 159 | self.setValue("MainWindow/geometry/%s" % layout, self._parent.saveGeometry()) 160 | self.setValue("MainWindow/windowState/%s" % layout, self._parent.saveState()) 161 | 162 | for dock in self._parent.findChildren(QtGui.QDockWidget): 163 | dock_name = dock.objectName() 164 | self.setValue("%s/geometry/%s" % (dock_name, layout), dock.saveGeometry()) 165 | 166 | def restoreLayout(self, layout): 167 | """ 168 | Restore a named layout. 169 | 170 | :param str layout: layout name to restore. 171 | """ 172 | log.info('restoring layout: "%s"' % layout) 173 | window_keys = self.window_keys() 174 | 175 | for widget_name in window_keys: 176 | key_name = '%s/geometry/%s' % (widget_name, layout) 177 | if widget_name != 'MainWindow': 178 | dock = self._parent.findChildren(QtGui.QDockWidget, widget_name) 179 | if dock: 180 | dock[0].restoreGeometry(value) 181 | else: 182 | if key_name in self.allKeys(): 183 | value = self.value(key_name) 184 | self._parent.restoreGeometry(value) 185 | 186 | window_state = '%s/windowState/%s' % (widget_name, layout) 187 | if window_state in self.allKeys(): 188 | self._parent.restoreState(self.value(window_state)) 189 | 190 | def deleteLayout(self, layout): 191 | """ 192 | Delete a named layout. 193 | 194 | :param str layout: layout name to restore. 195 | """ 196 | log.info('deleting layout: "%s"' % layout) 197 | window_keys = self.window_keys() 198 | 199 | for widget_name in window_keys: 200 | key_name = '%s/geometry/%s' % (widget_name, layout) 201 | if key_name in self.allKeys(): 202 | self.remove(key_name) 203 | 204 | if widget_name == 'MainWindow': 205 | window_state = '%s/windowState/%s' % (widget_name, layout) 206 | if window_state in self.allKeys(): 207 | self.remove(window_state) 208 | 209 | def getDefaultValue(self, key, *groups): 210 | """ 211 | Return the default values for a group. 212 | 213 | :param str key: key to search for. 214 | :returns: default value of key (None if not found) 215 | """ 216 | if self.group(): 217 | try: 218 | self.endGroup() 219 | except: 220 | pass 221 | 222 | result = None 223 | group_name = groups[0] 224 | for group in groups[1:]: 225 | group_name += "/%s" % group 226 | 227 | group_name += "/%s" % "default" 228 | group_name += "/%s" % key 229 | 230 | if group_name in self.allKeys(): 231 | result = self.value(group_name) 232 | return result 233 | 234 | def save(self, key='default'): 235 | """ 236 | Save, with optional category. 237 | 238 | * unused 239 | """ 240 | self.beginGroup("Mainwindow/%s" % key) 241 | self.setValue("size", QtCore.QSize(self._parent.width(), self._parent.height())) 242 | self.setValue("pos", self._parent.pos()) 243 | self.setValue("windowState", self._parent.saveState()) 244 | self.endGroup() 245 | 246 | def deleteFile(self): 247 | """ 248 | Delete the preferences file on disk. 249 | """ 250 | log.info('deleting settings: "%s"' % self.fileName()) 251 | return os.remove(self.fileName()) 252 | 253 | #- Recent Files ---- 254 | @property 255 | def recent_files(self): 256 | """ 257 | Returns a tuple of recent files, no larger than the max 258 | and reversed (for usage in menus). 259 | 260 | * unused 261 | """ 262 | files = self.getRecentFiles() 263 | tmp = [] 264 | for f in reversed(files): 265 | tmp.append(f) 266 | return tuple(tmp[:self._max_files]) 267 | 268 | def getRecentFiles(self): 269 | """ 270 | Get a tuple of the most recent files. 271 | """ 272 | recent_files = [] 273 | cnt = self.beginReadArray('RecentFiles') 274 | for i in range(cnt): 275 | self.setArrayIndex(i) 276 | fn = self.value('file') 277 | recent_files.append(fn) 278 | self.endArray() 279 | return tuple(recent_files) 280 | 281 | def addRecentFile(self, filename): 282 | """ 283 | Adds a recent file to the stack. 284 | """ 285 | recent_files = self.getRecentFiles() 286 | if filename in recent_files: 287 | recent_files = tuple(x for x in recent_files if x != filename) 288 | 289 | recent_files = recent_files + (filename,) 290 | self.beginWriteArray('RecentFiles') 291 | for i in range(len(recent_files)): 292 | self.setArrayIndex(i) 293 | self.setValue('file', recent_files[i]) 294 | self.endArray() 295 | 296 | def clearRecentFiles(self): 297 | self.remove('RecentFiles') 298 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import os 4 | 5 | 6 | __all__ = ['attr_type', 'auto_convert', 'camel_case_to_lower_case_underscore', 'camel_case_to_title', 'clean_name', 7 | 'is_bool', 'is_dict', 'is_list', 'is_none', 'is_number', 'is_string', 'list_attr_types', 8 | 'lower_case_underscore_to_camel_case', 'is_newer', 'test_func'] 9 | 10 | #- Naming ---- 11 | def clean_name(text): 12 | """ 13 | Return a cleaned version of a string - removes everything 14 | but alphanumeric characters and dots. 15 | 16 | :param str text: string to clean. 17 | :returns: cleaned string. 18 | :rtype: str 19 | """ 20 | return re.sub(r'[^a-zA-Z0-9\n\.]', '_', text) 21 | 22 | 23 | def camel_case_to_lower_case_underscore(text): 24 | """ 25 | Split string by upper case letters. 26 | F.e. useful to convert camel case strings to underscore separated ones. 27 | 28 | :param str text: string to convert. 29 | :returns: formatted string. 30 | :rtype: str 31 | """ 32 | words = [] 33 | from_char_position = 0 34 | for current_char_position, char in enumerate(text): 35 | if char.isupper() and from_char_position < text: 36 | words.append(s[from_char_position:current_char_position].lower()) 37 | from_char_position = current_char_position 38 | words.append(text[from_char_position:].lower()) 39 | return '_'.join(words) 40 | 41 | 42 | def camel_case_to_title(text): 43 | """ 44 | Split string by upper case letters and return a nice name. 45 | 46 | :param str text: string to convert. 47 | :returns: formatted string. 48 | :rtype: str 49 | """ 50 | words = [] 51 | from_char_position = 0 52 | for current_char_position, char in enumerate(text): 53 | if char.isupper() and from_char_position < current_char_position: 54 | words.append(text[from_char_position:current_char_position].title()) 55 | from_char_position = current_char_position 56 | words.append(text[from_char_position:].title()) 57 | return ' '.join(words) 58 | 59 | 60 | def lower_case_underscore_to_camel_case(text): 61 | """ 62 | Convert string or unicode from lower-case underscore to camel-case. 63 | 64 | :param str text: string to convert. 65 | :returns: formatted string. 66 | :rtype: str 67 | """ 68 | split_string = text.split('_') 69 | # use string's class to work on the string to keep its type 70 | class_ = text.__class__ 71 | return split_string[0] + class_.join('', map(class_.capitalize, split_string[1:])) 72 | 73 | 74 | #- Attribute Functions ---- 75 | def auto_convert(value): 76 | """ 77 | Auto-convert a value to it's given type. 78 | """ 79 | atype = attr_type(value) 80 | if atype == 'str': 81 | return str(value) 82 | 83 | if atype == 'bool': 84 | return bool(value) 85 | 86 | if atype == 'float': 87 | return float(value) 88 | 89 | if atype == 'int': 90 | return int(value) 91 | return value 92 | 93 | def attr_type(value): 94 | """ 95 | Determine the attribute type based on a value. 96 | Returns a string. 97 | 98 | For example: 99 | 100 | value = [2.1, 0.5] 101 | type = 'float2' 102 | 103 | :param value: attribute value. 104 | 105 | :returns: attribute type. 106 | :rtype: str 107 | """ 108 | if is_none(value): 109 | return 'null' 110 | 111 | if is_list(value): 112 | return list_attr_types(value) 113 | 114 | else: 115 | if is_bool(value): 116 | return 'bool' 117 | 118 | if is_string(value): 119 | return 'str' 120 | 121 | if is_number(value): 122 | if type(value) is float: 123 | return 'float' 124 | 125 | if type(value) is int: 126 | return 'int' 127 | return 'unknown' 128 | 129 | def list_attr_types(s): 130 | """ 131 | Return a string type for the value. 132 | 133 | .. todo:: 134 | - 'unknown' might need to be changed 135 | - we'll need a feature to convert valid int/str to floats 136 | ie: 137 | [eval(x) for x in s if type(x) in [str, unicode]] 138 | """ 139 | if not is_list(s): 140 | return 'unknown' 141 | 142 | for typ in [str, int, float, bool]: 143 | if all(isinstance(n, typ) for n in s): 144 | return '%s%d' % (typ.__name__, len(s)) 145 | 146 | if False not in list(set([is_number(x) for x in s])): 147 | return 'float%d' % len(s) 148 | return 'unknown' 149 | 150 | 151 | def is_none(s): 152 | return type(s).__name__ == 'NoneType' 153 | 154 | 155 | def is_string(s): 156 | return type(s) in [str, unicode] 157 | 158 | 159 | def is_number(s): 160 | """ 161 | Check if a string is a int/float 162 | """ 163 | if is_bool(s): 164 | return False 165 | return isinstance(s, int) or isinstance(s, float) 166 | 167 | 168 | def is_bool(s): 169 | """ 170 | Returns true if the object is a boolean value. 171 | * Updated to support custom decoders. 172 | """ 173 | return isinstance(s, bool) or str(s).lower() in ['true', 'false'] 174 | 175 | 176 | def is_list(s): 177 | """ 178 | Returns true if the object is a list type. 179 | """ 180 | return type(s) in [list, tuple] 181 | 182 | 183 | def is_dict(s): 184 | """ 185 | Returns true if the object is a dict type. 186 | """ 187 | from collections import OrderedDict 188 | return type(s) in [dict, OrderedDict] 189 | 190 | 191 | def is_newer(file1, file2): 192 | """ 193 | Returns true if file1 is newer than file2. 194 | 195 | :param str file1: first file to compare. 196 | :param str file2: second file to compare. 197 | 198 | :returns: file1 is newer. 199 | :rtype: bool 200 | """ 201 | if not os.path.exists(file1) or not os.path.exists(file2): 202 | return False 203 | 204 | time1 = os.path.getmtime(file1) 205 | time2 = os.path.getmtime(file2) 206 | return time1 > time2 207 | 208 | #- Testing ----- 209 | def test_func(w, h): 210 | print '# width: %.2f, height: %.2f' % (float(w), float(h)) 211 | 212 | 213 | def nodeParse(node): 214 | t = node[u"type"] 215 | 216 | if t == u"Program": 217 | body = [parse(block) for block in node[u"body"]] 218 | return Program(body) 219 | 220 | elif t == u"VariableDeclaration": 221 | kind = node[u"kind"] 222 | declarations = [parse(declaration) for declaration in node[u"declarations"]] 223 | return VariableDeclaration(kind, declarations) 224 | 225 | elif t == u"VariableDeclarator": 226 | id = parse(node[u"id"]) 227 | init = parse(node[u"init"]) 228 | return VariableDeclarator(id, init) 229 | 230 | elif t == u"Identifier": 231 | return Identifier(node[u"name"]) 232 | 233 | elif t == u"Literal": 234 | return Literal(node[u"value"]) 235 | 236 | elif t == u"BinaryExpression": 237 | operator = node[u"operator"] 238 | left = parse(node[u"left"]) 239 | right = parse(node[u"right"]) 240 | return BinaryExpression(operator, left, right) 241 | else: 242 | raise ValueError("Invalid data structure.") 243 | --------------------------------------------------------------------------------