class NodeGraphicsScene(GraphicsScene): """ A widget for displaying QGraphicsScene. """ # Background style show_background = d_(Bool(True)) background_grid_size = d_(Int(20)) background_grid_squares = d_(Int(5)) # color scheme color_light = d_(ColorMember("#2f2f2f")) color_dark = d_(ColorMember("#292929")) #: A reference to the ProxyNodeGraphicsScene object proxy = Typed(ProxyNodeGraphicsScene) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('show_background', 'background_grid_squares', 'background_grid_size', 'background', 'color_light', 'color_dark') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ super(NodeGraphicsScene, self)._update_proxy(change)
class Brush(Atom): """Defines the fill pattern""" #: Color color = ColorMember() #: Image image = Instance(Image) #: Style style = Enum( "solid", "dense1", "dense2", "dense3", "dense4", "dense5", "dense6", "dense7", "horizontal", "vertical", "cross", "diag", "bdiag", "fdiag", "linear", "radial", "conical", "texture", "none", ) #: Internal data _tkdata = Value()
class OccViewerClippedPlane(Control): #: A reference to the ProxySpinBox object. proxy = Typed(ProxyOccViewerClippedPlane) #: Enabled enabled = d_(Bool(True)) #: Capping capping = d_(Bool(True)) #: Hatched capping_hatched = d_(Bool(True)) #: Color capping_color = d_(ColorMember()) #: Position position = d_(Tuple(Float(strict=False), default=(0, 0, 0))) #: Direction direction = d_(Tuple(Float(strict=False), default=(1, 0, 0))) # ------------------------------------------------------------------------- # Observers # ------------------------------------------------------------------------- @observe('position', 'direction', 'enabled', 'capping', 'capping_hatched', 'capping_color') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(OccViewerClippedPlane, self)._update_proxy(change)
class ViewerLight(Atom): """ A Light in the view """ #: Whether the light is on enabled = Bool(True) #: Type of light type = Enum("directional", "spot", "ambient") #: Color of the light source color = ColorMember() #: Name of the light name = Str() #: Intensity of light source intensity = FloatRange(0.0, 1.0, 1.0) # ------------------------------------------------------------------------ # Directional light parameters # ------------------------------------------------------------------------ orientation = Enum( 'XnegYnegZneg', 'Xpos', 'Ypos', 'Zpos', 'Xneg', 'Yneg', 'Zneg', 'XposYpos', 'XposZpos', 'YposZpos', 'XnegYneg', 'XnegYpos', 'XnegZneg', 'XnegZpos', 'YnegZneg', 'YnegZpos', 'XposYneg', 'XposZneg', 'YposZneg', 'XposYposZpos', 'XposYnegZpos', 'XposYposZneg', 'XnegYposZpos', 'XposYnegZneg', 'XnegYposZneg', 'XnegYnegZpos', 'Zup_AxoLeft', 'Zup_AxoRight', 'Zup_Front', 'Zup_Back', 'Zup_Top', 'Zup_Bottom', 'Zup_Left', 'Zup_Right', 'Yup_AxoLeft', 'Yup_AxoRight', 'Yup_Front', 'Yup_Back', 'Yup_Top', 'Yup_Bottom', 'Yup_Left', 'Yup_Right') #: Headlight flag means that light position/direction are defined not in a #: World coordinate system, but relative to the camera orientation headlight = Bool() # ------------------------------------------------------------------------ # Spot light parameters # ------------------------------------------------------------------------ #: Position of the spot light position = Typed(Point) #: Direction of the spot light direction = Typed(Direction) def _default_direction(self): return Direction(-1, -1, -1) #: Range of the light source, 0.0 means infininte range = Float(0.0, strict=False) # Angle in radians of the cone created by the spot angle = FloatRange(0.0, math.pi, math.pi/2)
class Material(Atom): #: Name name = Str() def __init__(self, name="", **kwargs): """ Constructor which accepts a material name """ super().__init__(name=name, **kwargs) transparency = FloatRange(0.0, 1.0, 0.0) shininess = FloatRange(0.0, 1.0, 0.5) refraction_index = FloatRange(1.0, value=1.0) #: Color color = ColorMember() ambient_color = ColorMember() diffuse_color = ColorMember() specular_color = ColorMember() emissive_color = ColorMember()
class Dimension(ToolkitObject): """ Basic dimension """ #: Reference to the implementation control proxy = Typed(ProxyDimension) #: Whether the dimension should be displayed display = d_(Bool(True)) #: A string representing the color of the shape. color = d_(ColorMember()).tag(view=True, group='Display') def _default_color(self): return Color(0, 0, 0) #: A tuple or list of the (x, y, z) direction of this shape. This is #: coerced into a Point. The direction is relative to the dimensions axis. direction = d_(Coerced(Point, coercer=coerce_point)) def _default_direction(self): return Point(10, 10, 10) #: Set the flyout distance. flyout = d_(Float(0.0, strict=False)) #: Set the extension length (distance from arrow to text). extension_size = d_(Float(0.0, strict=False)) #: Set the arrow tail length. arrow_tail_size = d_(Float(0.0, strict=False)) #: List of shapes to create the dimension shapes = d_(List()) @observe('display', 'shapes', 'color', 'direction', 'flyout', 'extension_size', 'arrow_tail_size') def _update_proxy(self, change): super(Dimension, self)._update_proxy(change) def show(self): """ Generates the dimension Returns ------- dimension: AIS_Dimension The dimension generated by this declaration. """ if not self.is_initialized: self.initialize() if not self.proxy_is_active: self.activate_proxy() return self.proxy.dimension
class DisplayItem(ToolkitObject): """ Basic display item. This represents an item in the display that has no effect on the model. """ #: Reference to the implementation control proxy = Typed(ProxyDisplayItem) #: Whether the item should be displayed display = d_(Bool(True)) #: A string representing the color of the shape. color = d_(ColorMember()).tag(view=True, group='Display') def _default_color(self): return Color(0, 0, 0) #: A tuple or list of the (x, y, z) direction of this shape. This is #: coerced into a Point. The direction is relative to the dimensions axis. position = d_(Coerced(Point, coercer=coerce_point)) def _default_position(self): return Point(0, 0, 0) #: A tuple or list of the (x, y, z) direction of this shape. This is #: coerced into a Point. The direction is relative to the dimensions axis. direction = d_(Coerced(Direction, coercer=coerce_direction)) def _default_direction(self): return Point(0, 0, 1) @observe('position', 'color', 'direction') def _update_proxy(self, change): super()._update_proxy(change) def show(self): """ Generates the display item Returns ------- item: Graphic3d item The item generated by this declaration. """ if not self.is_initialized: self.initialize() if not self.proxy_is_active: self.activate_proxy() return self.proxy.item
class Brush(Atom): """ Defines the fill pattern """ #: Color color = ColorMember() #: Image image = Instance(Image) #: Style style = Enum('solid', 'dense1', 'dense2', 'dense3', 'dense4', 'dense5', 'dense6', 'dense7', 'horizontal', 'vertical', 'cross', 'diag', 'bdiag', 'fdiag', 'linear', 'radial', 'conical', 'texture', 'none') #: Internal data _tkdata = Value()
class FillColorFilter(JobFilter): type = "fill-color" style_attr = 'fill' #: The color color = ColorMember() #: The color name data = Unicode() @classmethod def get_filter_options(cls, job, doc): svg = doc._e colors = [] used = set() for e in svg.xpath('//*[@style]'): style = get_node_style(e) color = style.get(cls.style_attr) if color is not None and color not in used: used.add(color) # Try to look up a common name label = SVG_COLOR_NAMES.get(color.lower(), color) colors.append(cls(name=label, color=color, data=color)) return colors def apply_filter(self, job, doc): """ Remove all subpaths from doc that are in this layer by reparsing the xml. """ # Copy it since we're modifying svg = copy.deepcopy(doc._e) # Remove all nodes with that stroke style for e in svg.xpath('//*[@style]'): style = get_node_style(e) if style.get(self.style_attr) == self.data: e.getparent().remove(e) return QtSvgDoc(svg, parent=True)
class Pen(Atom): #: Color color = ColorMember() #: Width width = Float(1.0, strict=False) #: Line Style line_style = Enum('solid', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'custom', 'none') #: Cap Style cap_style = Enum('square', 'flat', 'round') #: Join Style join_style = Enum('bevel', 'miter', 'round') #: Dash pattern used when line_style is 'custom' dash_pattern = List(Float(strict=False)) #: Internal data _tkdata = Value()
class Pen(Atom): #: Color color = ColorMember() #: Width width = Float(1.0, strict=False) #: Line Style line_style = Enum( "solid", "dash", "dot", "dash_dot", "dash_dot_dot", "custom", "none" ) #: Cap Style cap_style = Enum("square", "flat", "round") #: Join Style join_style = Enum("bevel", "miter", "round") #: Dash pattern used when line_style is 'custom' dash_pattern = List(Float(strict=False)) #: Internal data _tkdata = Value()
class Plot1DLine(Plot1D): """""" #: Color of the plot color = ColorMember() #: Weight of the line line_weight = Float(1.0) #: Style of the line line_style = Str() #: Should markers be displayed markers_enabled = Bool() #: Size of the markers markers_size = Float() #: Shape of the marker # FIXME complete marker_shape = Enum(( "*", "+", ))
class QtNodeGraphicsScene(QtGraphicsScene, ProxyNodeGraphicsScene): """ A Qt implementation of an Enaml ProxyNodeGraphicsScene widget. """ #: A reference to the widget created by the proxy. widget = Typed(QGraphicsScene) # Background style show_background = Bool(True) background_grid_size = Int(20) background_grid_squares = Int(5) # color scheme color_light = ColorMember("#2f2f2f") color_dark = ColorMember("#292929") pen_light = Typed(QtGui.QPen) pen_dark = Typed(QtGui.QPen) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying html widget. """ widget = QGraphicsScene( self, self.parent_widget(), ) self.widget = widget def init_widget(self): """ Initialize the underlying widget. """ super(QtNodeGraphicsScene, self).init_widget() d = self.declaration self.pen_light = QtGui.QPen(QtGui.QColor.fromRgba(d.color_light.argb)) self.pen_light.setWidth(1) self.pen_dark = QtGui.QPen(QtGui.QColor.fromRgba(d.color_dark.argb)) self.pen_dark.setWidth(2) self.show_background = d.show_background self.background_grid_size = d.background_grid_size self.background_grid_squares = d.background_grid_squares self.color_light = d.color_light self.color_dark = d.color_dark #-------------------------------------------------------------------------- # QGraphicsScene callbacks #-------------------------------------------------------------------------- def on_draw_background(self, painter, rect): if not self.show_background: return # here we create our grid left = int(math.floor(rect.left())) right = int(math.ceil(rect.right())) top = int(math.floor(rect.top())) bottom = int(math.ceil(rect.bottom())) first_left = left - (left % self.background_grid_size) first_top = top - (top % self.background_grid_size) # compute all lines to be drawn lines_light, lines_dark = [], [] for x in range(first_left, right, self.background_grid_size): if x % (self.background_grid_size * self.background_grid_squares) != 0: lines_light.append(QtCore.QLine(x, top, x, bottom)) else: lines_dark.append(QtCore.QLine(x, top, x, bottom)) for y in range(first_top, bottom, self.background_grid_size): if y % (self.background_grid_size * self.background_grid_squares) != 0: lines_light.append(QtCore.QLine(left, y, right, y)) else: lines_dark.append(QtCore.QLine(left, y, right, y)) # draw the lines if lines_light: painter.setPen(self.pen_light) painter.drawLines(*lines_light) if lines_dark: painter.setPen(self.pen_dark) painter.drawLines(*lines_dark) #-------------------------------------------------------------------------- # QGraphicsScene API #-------------------------------------------------------------------------- def set_show_background(self, show_background): self.show_background = show_background def set_background_grid_squares(self, background_grid_squares): self.background_grid_squares = background_grid_squares def set_background_grid_size(self, background_grid_size): self.background_grid_size = background_grid_size def set_color_light(self, color_light): self.color_light = color_light def set_color_dark(self, color_dark): self.color_dark = color_dark
class Widget(ToolkitObject, Stylable): """ The base class of visible widgets in Enaml. """ #: Whether or not the widget is enabled. enabled = d_(Bool(True)) #: Whether or not the widget is visible. visible = d_(Bool(True)) #: The background color of the widget. background = d_(ColorMember()) #: The foreground color of the widget. foreground = d_(ColorMember()) #: The font used for the widget. font = d_(FontMember()) #: The minimum size for the widget. The default means that the #: client should determine an intelligent minimum size. minimum_size = d_(Coerced(Size, (-1, -1))) #: The maximum size for the widget. The default means that the #: client should determine an intelligent maximum size. maximum_size = d_(Coerced(Size, (-1, -1))) #: The tool tip to show when the user hovers over the widget. tool_tip = d_(Str()) #: The status tip to show when the user hovers over the widget. status_tip = d_(Str()) #: Set the extra features to enable for this widget. This value must #: be provided when the widget is instantiated. Runtime changes to #: this value are ignored. features = d_(Coerced(Feature.Flags)) #: A reference to the ProxyWidget object. proxy = Typed(ProxyWidget) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('enabled', 'visible', 'background', 'foreground', 'font', 'minimum_size', 'maximum_size', 'tool_tip', 'status_tip') def _update_proxy(self, change): """ Update the proxy widget when the Widget data changes. This method only updates the proxy when an attribute is updated; not when it is created or deleted. """ # The superclass implementation is sufficient. super(Widget, self)._update_proxy(change) #-------------------------------------------------------------------------- # Reimplementations #-------------------------------------------------------------------------- def restyle(self): """ Restyle the toolkit widget. This method is invoked by the Stylable class when the style dependencies have changed for the widget. This will trigger a proxy restyle if necessary. This method should not typically be called directly by user code. """ if self.proxy_is_active: self.proxy.restyle() #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def show(self): """ Ensure the widget is shown. Calling this method will also set the widget visibility to True. """ self.visible = True if self.proxy_is_active: self.proxy.ensure_visible() def hide(self): """ Ensure the widget is hidden. Calling this method will also set the widget visibility to False. """ self.visible = False if self.proxy_is_active: self.proxy.ensure_hidden() def set_focus(self): """ Set the keyboard input focus to this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.set_focus() def clear_focus(self): """ Clear the keyboard input focus from this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.clear_focus() def has_focus(self): """ Test whether this widget has input focus. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! Returns ------- result : bool True if this widget has input focus, False otherwise. """ if self.proxy_is_active: return self.proxy.has_focus() return False def focus_next_child(self): """ Give focus to the next widget in the focus chain. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.focus_next_child() def focus_previous_child(self): """ Give focus to the previous widget in the focus chain. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.focus_previous_child() @d_func def next_focus_child(self, current): """ Compute the next widget which should gain focus. When the FocusTraversal feature of the widget is enabled, this method will be invoked as a result of a Tab key press or from a call to the 'focus_next_child' method on a decendant of the owner widget. It should be reimplemented in order to provide custom logic for computing the next focus widget. ** The FocusTraversal feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- current : Widget or None The current widget with input focus, or None if no widget has focus or if the toolkit widget with focus does not correspond to an Enaml widget. Returns ------- result : Widget or None The next widget which should gain focus, or None to follow the default toolkit behavior. """ return None @d_func def previous_focus_child(self, current): """ Compute the previous widget which should gain focus. When the FocusTraversal feature of the widget is enabled, this method will be invoked as a result of a Shift+Tab key press or from a call to the 'focus_prev_child' method on a decendant of the owner widget. It should be reimplemented in order to provide custom logic for computing the previous focus widget. ** The FocusTraversal feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- current : Widget or None The current widget with input focus, or None if no widget has focus or if the toolkit widget with focus does not correspond to an Enaml widget. Returns ------- result : Widget or None The previous widget which should gain focus, or None to follow the default toolkit behavior. """ return None @d_func def focus_gained(self): """ A method invoked when the widget gains input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def focus_lost(self): """ A method invoked when the widget loses input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drag_start(self): """ A method called at the start of a drag-drop operation. This method is called when the user starts a drag operation by dragging the widget with the left mouse button. It returns the drag data for the drag operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Returns ------- result : DragData An Enaml DragData object which holds the drag data. If this is not provided, no drag operation will occur. """ return None @d_func def drag_end(self, drag_data, result): """ A method called at the end of a drag-drop operation. This method is called after the user has completed the drop operation by releasing the left mouse button. It is passed the original drag data object along with the resulting drop action of the operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- data : DragData The drag data created by the `drag_start` method. result : DropAction The requested drop action when the drop completed. """ pass @d_func def drag_enter(self, event): """ A method invoked when a drag operation enters the widget. The widget should inspect the mime data of the event and accept the event if it can handle the drop action. The event must be accepted in order to receive further drag-drop events. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_move(self, event): """ A method invoked when a drag operation moves in the widget. This method will not normally be implemented, but it can be useful for supporting advanced drag-drop interactions. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_leave(self): """ A method invoked when a drag operation leaves the widget. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drop(self, event): """ A method invoked when the user drops the data on the widget. The widget should either accept the proposed action, or set the drop action to an appropriate action before accepting the event, or set the drop action to DropAction.Ignore and then ignore the event. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass
class ViewerPlugin(Plugin): # ------------------------------------------------------------------------- # Default viewer settings # ------------------------------------------------------------------------- background_mode = Enum('gradient', 'solid').tag(config=True, viewer='background') background_top = ColorMember('lightgrey').tag(config=True, viewer='background') background_bottom = ColorMember('grey').tag(config=True, viewer='background') background_fill_method = Enum( 'corner3', 'corner1', 'corner2', 'corner4', 'ver', 'hor', 'diag1', 'diag2', ).tag(config=True, viewer='background') trihedron_mode = Str('right-lower').tag(config=True, viewer=True) #: Defaults shape_color = ColorMember('steelblue').tag(config=True, viewer=True) #: Grid options grid_mode = Str().tag(config=True, viewer=True) grid_major_color = ColorMember('#444').tag(config=True, viewer='grid_colors') grid_minor_color = ColorMember('#888').tag(config=True, viewer='grid_colors') #: Rendering options antialiasing = Bool(True).tag(config=True, viewer=True) raytracing = Bool(True).tag(config=True, viewer=True) draw_boundaries = Bool(True).tag(config=True, viewer=True) shadows = Bool(True).tag(config=True, viewer=True) reflections = Bool(True).tag(config=True, viewer=True) chordial_deviation = Float(0.001).tag(config=True, viewer=True) # ------------------------------------------------------------------------- # Plugin members # ------------------------------------------------------------------------- #: Default dir for screenshots screenshot_dir = Str().tag(config=True) #: Exporters exporters = ContainerList() def get_viewer_members(self): for m in self.members().values(): meta = m.metadata if not meta: continue if meta.get('viewer'): yield m def get_viewers(self): ViewerDockItem = viewer_factory() dock = self.workbench.get_plugin('declaracad.ui').get_dock_area() for item in dock.dock_items(): if isinstance(item, ViewerDockItem): yield item def fit_all(self, event=None): return viewer = self.get_viewer() viewer.proxy.display.FitAll() def run(self, event=None): viewer = self.get_viewer() editor = self.workbench.get_plugin('declaracad.editor').get_editor() doc = editor.doc viewer.renderer.set_source(editor.get_text()) doc.version += 1 def get_viewer(self, name=None): for viewer in self.get_viewers(): if name is None: return viewer elif viewer.name == name: return viewer def _default_exporters(self): """ TODO: push to an ExtensionPoint """ from .exporters.stl.exporter import StlExporter from .exporters.step.exporter import StepExporter return [StlExporter, StepExporter] # ------------------------------------------------------------------------- # Plugin commands # ------------------------------------------------------------------------- def export(self, event): """ Export the current model to stl """ options = event.parameters.get('options') if not options: raise ValueError("An export `options` parameter is required") # Pickle the configured exporter and send it over cmd = [sys.executable] if not sys.executable.endswith('declaracad'): cmd.extend(['-m', 'declaracad']) data = jsonpickle.dumps(options) assert data != 'null', f"Exporter failed to serialize: {options}" cmd.extend(['export', data]) log.debug(" ".join(cmd)) protocol = ProcessLineReceiver() loop = asyncio.get_event_loop() deferred_call(loop.subprocess_exec, lambda: protocol, *cmd) return protocol def screenshot(self, event): """ Export the views as a screenshot """ if 'options' not in event.parameters: editor = self.workbench.get_plugin('declaracad.editor') filename = editor.active_document.name options = ScreenshotOptions(filename=filename, default_dir=self.screenshot_dir) else: options = event.parameters.get('options') # Update the default screenshot dir self.screenshot_dir, _ = os.path.split(options.path) results = [] if options.target: viewer = self.get_viewer(options.target) if viewer: results.append(viewer.renderer.screenshot(options.path)) else: for i, viewer in enumerate(self.get_viewers()): # Insert view number path, ext = os.path.splitext(options.path) filename = "{}-{}{}".format(path, i + 1, ext) results.append(viewer.renderer.screenshot(filename)) return results
class NodeSocket(GraphicsItem): """ A node-item in a node graph """ id = d_(Str()) name = d_(Unicode()) index = d_(Int(0)) socket_type = d_(Typed(SocketType)) socket_position = d_(Typed(SocketPosition)) socket_spacing = d_(Float(22)) radius = d_(Float(6.0)) outline_width = d_(Float(1.0)) font_label = d_(FontMember('10pt Ubuntu')) color_label = d_(ColorMember("#AAAAAAFF")) color_background = d_(ColorMember("#FF7700FF")) color_outline = d_(ColorMember("#000000FF")) show_label = d_(Bool(True)) relative_position = Typed(Point2D) edges = ContainerList(EdgeItem) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) #: A reference to the ProxyComboBox object. proxy = Typed(ProxyNodeSocket) absolute_position = Property( lambda self: self.parent.position + self.relative_position, cached=False) def _default_name(self): return self.id #-------------------------------------------------------------------------- # Content Handlers #-------------------------------------------------------------------------- def destroy(self): if self.scene is not None: for edge in self.edges[:]: self.scene.controller.destroy_edge(edge.id) super(NodeSocket, self).destroy() #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('name', 'socket_type', 'relative_position', 'radius', 'outline_width', 'color_background', 'color_outline') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(NodeSocket, self)._update_proxy(change) # @todo: changes need to propagate to the parent ... self.request_update() @observe('socket_position', 'index', 'socket_spacing') def _update_relative_position(self, change): if not self._guard & SOCKET_COMPUTE_HEIGHT_GUARD: self.update_sockets() def _observe_visible(self, change): if self.initialized: from .node_item import NodeItem node = self.parent if isinstance(node, NodeItem): node.recompute_node_layout() #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def update_sockets(self): self.relative_position = self.compute_socket_position() def compute_socket_position(self): self._guard |= SOCKET_COMPUTE_HEIGHT_GUARD result = Point2D(x=0, y=0) from .node_item import NodeItem node = self.parent if isinstance(node, NodeItem): x = 0 if (self.socket_position in (SocketPosition.LEFT_TOP, SocketPosition.LEFT_BOTTOM)) else node.width if self.socket_position in (SocketPosition.LEFT_BOTTOM, SocketPosition.RIGHT_BOTTOM): # start from bottom y = node.height - node.edge_size - node.padding - self.index * self.socket_spacing else: # start from top y = node.title_height + node.padding + node.edge_size + self.index * self.socket_spacing result = Point2D(x=x, y=y) self._guard &= ~SOCKET_COMPUTE_HEIGHT_GUARD return result
class GraphicsScene(ToolkitObject, Stylable): """ The base class of visible GraphicsScene in Enaml. """ # dimensions width = d_(Int(6400)) height = d_(Int(6400)) #: The background color of the widget. background = d_(ColorMember()) #: The font used for the widget. font = d_(FontMember()) #: The minimum render size for the scene. minimum_render_size = d_(Int(0)) #: The tool tip to show when the user hovers over the widget. tool_tip = d_(Unicode()) #: The status tip to show when the user hovers over the widget. status_tip = d_(Unicode()) def _get_items(self): return [c for c in self.children if isinstance(c, GraphicsItem)] #: Internal item reference _items = Property(lambda self: self._get_items(), cached=True) #: Set the extra features to enable for this widget. This value must #: be provided when the widget is instantiated. Runtime changes to #: this value are ignored. features = d_(Coerced(Feature.Flags)) guard = d_(Coerced(SceneGuard.Flags)) #: Nodegraph Controller controller = d_(ForwardInstance(import_graph_controller_class)) nodes = Dict(Unicode(), NodeItem) edges = Dict(Unicode(), EdgeItem) #: private dict to generate consecutive ids for item types _item_id_generator = Dict() #: A reference to the ProxyGraphicsScene object. proxy = Typed(ProxyGraphicsScene) #-------------------------------------------------------------------------- # Content Handlers #-------------------------------------------------------------------------- def child_added(self, child): """ Reset the item cache when a child is added """ self.guard = SceneGuard.INITIALIZING if isinstance(child, GraphicsItem): self.add_item(child) super(GraphicsScene, self).child_added(child) self.get_member('_items').reset(self) self.guard = SceneGuard.NOOP def child_removed(self, child): """ Reset the item cache when a child is removed """ self.guard = SceneGuard.INITIALIZING super(GraphicsScene, self).child_removed(child) self.get_member('_items').reset(self) if isinstance(child, GraphicsItem): self.delete_item(child) self.guard = SceneGuard.NOOP def activate_bottom_up(self): super(GraphicsScene, self).activate_bottom_up() self.set_scene_rect(self.bounding_box_all_nodes()) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('width', 'height', 'background', 'font', 'minimum_render_size', 'tool_tip', 'status_tip') def _update_proxy(self, change): """ Update the proxy widget when the Widget data changes. This method only updates the proxy when an attribute is updated; not when it is created or deleted. """ # The superclass implementation is sufficient. super(GraphicsScene, self)._update_proxy(change) self.proxy.update() #-------------------------------------------------------------------------- # Reimplementations #-------------------------------------------------------------------------- def restyle(self): """ Restyle the toolkit graphicsscene. This method is invoked by the Stylable class when the style dependencies have changed for the graphicsscene. This will trigger a proxy restyle if necessary. This method should not typically be called directly by user code. """ if self.proxy_is_active: self.proxy.restyle() #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def update(self, *args): self.proxy.update(*args) def add_item(self, item): item.set_scene(self) if isinstance(item, NodeItem): self.nodes[item.id] = item elif isinstance(item, EdgeItem): self.edges[item.id] = item def delete_item(self, item): item.set_scene(None) if isinstance(item, NodeItem): self.nodes.pop(item.id, None) elif isinstance(item, EdgeItem): self.edges.pop(item.id, None) def update_item_id(self, item, id): if isinstance(item, NodeItem): self.nodes.pop(item.id, None) item.id = id self.nodes[id] = item elif isinstance(item, EdgeItem): self.edges.pop(item.id, None) item.id = id self.edges[id] = item def clear_all(self): for edge in list(self.edges.values())[:]: edge.destroy() for node in list(self.nodes.values())[:]: node.destroy() def generate_item_id(self, prefix, cls): id = self._item_id_generator.get(cls, 0) existing_ids = list(self.nodes.keys()) + list(self.edges.keys()) found_id = False while not found_id: id += 1 item_id = "%s-%00d" % (prefix, id) if item_id not in existing_ids: found_id = True self._item_id_generator[cls] = id return item_id def bounding_box_all_nodes(self): x_min = [] y_min = [] x_max = [] y_max = [] for node in self.nodes.values(): x_min.append(node.position.x) x_max.append(node.position.x + node.width) y_min.append(node.position.y) y_max.append(node.position.y + node.height) if len(x_min) == 0: return [0, 0, 800, 600] x = min(x_min) y = min(y_min) return [x, y, max(x_max) - x, max(y_max) - y] def set_scene_rect(self, bbox): self.proxy.update_scenerect(bbox) def set_focus(self): """ Set the keyboard input focus to this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.set_focus() def clear_focus(self): """ Clear the keyboard input focus from this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.clear_focus() def has_focus(self): """ Test whether this widget has input focus. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! Returns ------- result : bool True if this widget has input focus, False otherwise. """ if self.proxy_is_active: return self.proxy.has_focus() return False @d_func def focus_gained(self): """ A method invoked when the widget gains input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def focus_lost(self): """ A method invoked when the widget loses input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drag_start(self): """ A method called at the start of a drag-drop operation. This method is called when the user starts a drag operation by dragging the widget with the left mouse button. It returns the drag data for the drag operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Returns ------- result : DragData An Enaml DragData object which holds the drag data. If this is not provided, no drag operation will occur. """ return None @d_func def drag_end(self, drag_data, result): """ A method called at the end of a drag-drop operation. This method is called after the user has completed the drop operation by releasing the left mouse button. It is passed the original drag data object along with the resulting drop action of the operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- data : DragData The drag data created by the `drag_start` method. result : DropAction The requested drop action when the drop completed. """ pass @d_func def drag_enter(self, event): """ A method invoked when a drag operation enters the widget. The widget should inspect the mime data of the event and accept the event if it can handle the drop action. The event must be accepted in order to receive further drag-drop events. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_move(self, event): """ A method invoked when a drag operation moves in the widget. This method will not normally be implemented, but it can be useful for supporting advanced drag-drop interactions. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_leave(self): """ A method invoked when a drag operation leaves the widget. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drop(self, event): """ A method invoked when the user drops the data on the widget. The widget should either accept the proposed action, or set the drop action to an appropriate action before accepting the event, or set the drop action to DropAction.Ignore and then ignore the event. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass
class OccViewer(Control): """ A widget to view OpenCascade shapes. """ #: A reference to the ProxySpinBox object. proxy = Typed(ProxyOccViewer) #: Bounding box of displayed shapes. A tuple of the following values #: (xmin, ymin, zmin, xmax, ymax, zmax). bbox = d_(Typed(BBox), writable=False) #: Display mode display_mode = d_(Enum('shaded', 'wireframe')) #: Selection mode selection_mode = d_(Enum( 'any', 'shape', 'shell', 'face', 'edge', 'wire', 'vertex')) #: Selected items selection = d_(Typed(ViewerSelection), writable=False) #: View direction view_mode = d_(Enum('iso', 'top', 'bottom', 'left', 'right', 'front', 'rear')) # ------------------------------------------------------------------------- # Grid # ------------------------------------------------------------------------- grid_mode = d_(Enum('', 'rectangular-lines', 'rectangular-points', 'circular-lines', 'circular-points')) grid_colors = d_(Coerced(tuple, coercer=color_pair_coercer)) def _default_grid_colors(self): return (parse_color('#888'), parse_color('#444')) #: Selection event #reset_view = d_(Event(),writable=False) #: Show tahedron trihedron_mode = d_(Enum('right-lower', 'right-upper', 'left-lower', 'left-upper')) #: Background gradient this is corecred from a of strings background_gradient = d_(Coerced(tuple, coercer=color_pair_coercer)) def _default_background_gradient(self): return (parse_color('white'), parse_color('silver')) #: Default shape rendering color if none is defined shape_color = d_(ColorMember('steelblue')) #: Display shadows shadows = d_(Bool(True)) #: Display reflections reflections = d_(Bool(True)) #: Enable antialiasing antialiasing = d_(Bool(True)) #: Enable raytracing raytracing = d_(Bool(True)) #: Raytracing depth raytracing_depth = d_(Int(3)) #: Enable hidden line removal hidden_line_removal = d_(Bool(False)) #: Draw face boundaries draw_boundaries = d_(Bool(False)) #: View expands freely in width by default. hug_width = set_default('ignore') #: View expands freely in height by default. hug_height = set_default('ignore') #: Lock rotation so the mouse cannot not rotate lock_rotation = d_(Bool()) #: Lock zoom so the mouse wheel cannot not zoom lock_zoom = d_(Bool()) #: Lights lights = d_(List(ViewerLight)) def _default_lights(self): headlight = ViewerLight( type="directional", color="white", headlight=True) ambient = ViewerLight(type="ambient", color="white", intensity=0.95) return [headlight, ambient] #: Chordial Deviation. This is the "smoothness" of curves chordial_deviation = d_(Float(0.001, strict=False)) #: Events #: Raise StopIteration to indicate handling should stop key_pressed = d_(Event(), writable=False) mouse_pressed = d_(Event(), writable=False) mouse_released = d_(Event(), writable=False) mouse_wheeled = d_(Event(), writable=False) mouse_moved = d_(Event(), writable=False) #: Loading status loading = d_(Bool(), writable=False) progress = d_(Float(strict=False), writable=False) # ------------------------------------------------------------------------- # Observers # ------------------------------------------------------------------------- @observe('position', 'display_mode', 'view_mode', 'trihedron_mode', 'selection_mode', 'background_gradient', 'double_buffer', 'shadows', 'reflections', 'antialiasing', 'lock_rotation', 'lock_zoom', 'draw_boundaries', 'hidden_line_removal', 'shape_color', 'raytracing_depth', 'lights', 'grid_mode', 'grid_colors') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(OccViewer, self)._update_proxy(change) # ------------------------------------------------------------------------- # Viewer API # ------------------------------------------------------------------------- def fit_all(self): """ Zoom in and center on all item(s) """ self.proxy.fit_all() def fit_selection(self): """ Zoom in and center on the selected item(s) """ self.proxy.fit_selection() def take_screenshot(self, filename): """ Take a screenshot and save it with the given filename """ self.proxy.take_screenshot(filename) def zoom_factor(self, factor): """ Zoom in by a given factor """ self.proxy.zoom_factor(factor) def rotate_view(self, *args, **kwargs): """ Rotate by the given number of degrees about the current axis""" self.proxy.rotate_view(*args, **kwargs) def turn_view(self, *args, **kwargs): """ Rotate by the given number of degrees about the current axis""" self.proxy.turn_view(*args, **kwargs) def reset_view(self): """ Reset zoom to defaults """ self.proxy.reset_view() def clear_display(self): """ Clear the display, all children should be removed before calling this or they'll be rerendered. """ self.proxy.clear_display()
class Shape(ToolkitObject): """ Abstract shape component that can be displayed on the screen and represented by the framework. Notes ------ This shape's proxy holds an internal reference to the underlying shape which can be accessed using `self.proxy.shape` if needed. The topology of the shape can be accessed using the `self.proxy.topology` attribute. """ #: Reference to the implementation control proxy = Typed(ProxyShape) #: Whether the shape should be displayed and exported when in a part #: If set to false this shape will be excluded from the rendered part display = d_(Bool(True)) #: The tolerance to use for operations that may require it. tolerance = d_(Float(10**-6, strict=False)) #: Material material = d_(Coerced(Material)) #: A string representing the color of the shape. color = d_(ColorMember()) #: The opacity of the shape used for display. transparency = d_(FloatRange(0.0, 1.0, 0.0)) #: Texture to apply to the shape texture = d_(Instance(Texture)) #: Position alias def _get_x(self): return self.position.x def _set_x(self, v): self.position.x = v x = d_(Property(_get_x, _set_x)) def _get_y(self): return self.position.y def _set_y(self, v): self.position.y = v y = d_(Property(_get_y, _set_y)) def _get_z(self): return self.position.z def _set_z(self, v): self.position.z = v z = d_(Property(_get_z, _set_z)) #: A tuple or list of the (x, y, z) position of this shape. This is #: coerced into a Point. position = d_(Coerced(Point, coercer=coerce_point)) def _default_position(self): return Point(0, 0, 0) #: A tuple or list of the (u, v, w) normal vector of this shape. This is #: coerced into a Vector setting the orentation of the shape. #: This is effectively the working plane. direction = d_(Coerced(Direction, coercer=coerce_direction)) def _default_direction(self): return Direction(0, 0, 1) #: Rotation about the normal vector in radians rotation = d_(Coerced(float, coercer=coerce_rotation)) def _get_axis(self): return (self.position, self.direction, self.rotation) def _set_axis(self, axis): self.position, self.direction, self.rotation = axis #: A tuple or list of the (u, v, w) axis of this shape. This is #: coerced into a Vector that defines the x, y, and z orientation of #: this shape. axis = d_(Property(_get_axis, _set_axis)) def _get_topology(self): return self.proxy.topology #: A read only property that accesses the topology of the shape such #: as edges, faces, shells, solids, etc.... topology = Property(_get_topology, cached=True) def _get_bounding_box(self): if self.proxy.shape: try: return self.proxy.get_bounding_box() except: pass #: Bounding box of this shape bbox = Property(_get_bounding_box, cached=True) @observe('color', 'transparency', 'display', 'texture') def _update_proxy(self, change): super(Shape, self)._update_proxy(change) @observe('proxy.shape') def _update_properties(self, change): """ Clear the cached references when the shape changes. """ for k in ('bbox', 'topology'): self.get_member(k).reset(self) self.constructed() #: Triggered when the shape is constructed constructed = d_(Event(), writable=False) def render(self): """ Generates and returns the actual shape from the declaration. Enaml does this automatically when it's included in the viewer so this is only neede when working with shapes manually. Returns ------- shape: TopoDS_Shape The shape generated by this declaration. """ if not self.is_initialized: self.initialize() if not self.proxy_is_active: self.activate_proxy() return self.proxy.shape
class NodeItem(GraphicsItem): """ A node-item in a node graph """ id = d_(Str()) name = d_(Unicode()) width = d_(Int(180)) height = d_(Int()) position = d_(Typed(Point2D)) edge_size = d_(Float(10.0)) title_height = d_(Float(24.0)) padding = d_(Float(4.0)) color_default = d_(ColorMember("#0000007F")) color_selected = d_(ColorMember("#FFA637FF")) font_title = d_(FontMember('10pt Ubuntu')) color_title = d_(ColorMember("#AAAAAAFF")) color_title_background = d_(ColorMember("#313131FF")) color_background = d_(ColorMember("#212121E3")) show_content_inline = d_(Bool(False)) #: the model item from the underlying graph structure model = d_(Typed(Atom)) context_menu_event = d_(Event()) recompute_node_layout = d_(Event()) #: optional Node Content content = Instance(NodeContent) input_sockets = List(NodeSocket) output_sockets = List(NodeSocket) input_sockets_visible = Property(lambda self: [s for s in self.input_sockets if s.visible], cached=True) output_sockets_visible = Property(lambda self: [s for s in self.output_sockets if s.visible], cached=True) input_sockets_dict = Property(lambda self: self._mk_input_dict(), cached=True) output_sockets_dict = Property(lambda self: self._mk_output_dict(), cached=True) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) #: A reference to the ProxyComboBox object. proxy = Typed(ProxyNodeItem) def _default_position(self): return Point2D(x=0, y=0) def _default_height(self): return self.compute_height() def _default_id(self): if self.scene is not None: cls = self.__class__ return self.scene.generate_item_id(cls.__name__, cls) return "<undefined>" def _default_name(self): return self.id #-------------------------------------------------------------------------- # Content Handlers #-------------------------------------------------------------------------- def child_added(self, child): """ Reset the item cache when a child is added """ super(NodeItem, self).child_added(child) if isinstance(child, NodeContent): self.content = child if isinstance(child, NodeSocket): if child.socket_type == SocketType.INPUT: self.input_sockets.append(child) self.get_member('input_sockets_dict').reset(self) elif child.socket_type == SocketType.OUTPUT: self.output_sockets.append(child) self.get_member('output_sockets_dict').reset(self) def child_removed(self, child): """ Reset the item cache when a child is removed """ super(NodeItem, self).child_removed(child) if isinstance(child, NodeContent): self.content = None if isinstance(child, NodeSocket): if child.socket_type == SocketType.INPUT: self.input_sockets.remove(child) self.get_member('input_sockets_dict').reset(self) elif child.socket_type == SocketType.OUTPUT: self.output_sockets.remove(child) self.get_member('output_sockets_dict').reset(self) def _mk_input_dict(self): return {c.id: c for c in self.children if isinstance(c, NodeSocket) and c.socket_type == SocketType.INPUT} def _mk_output_dict(self): return {c.id: c for c in self.children if isinstance(c, NodeSocket) and c.socket_type == SocketType.OUTPUT} def activate_bottom_up(self): self.assign_socket_indices() #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('id', 'name', 'width', 'height', 'edge_size', 'title_height', 'padding', 'color_default', 'color_selected', 'color_title', 'color_title_background', 'color_background', 'show_content_inline', 'content') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(NodeItem, self)._update_proxy(change) self.request_update() @observe('width', 'height', 'edge_size', 'title_height', 'padding') def _update_layout(self, change): for s in self.input_sockets + self.output_sockets: s.update_sockets() if self.content is not None: self.content.update_content_geometry() def _observe_recompute_node_layout(self, change): if self.initialized: if not self._guard & NODE_UPDATE_LAYOUT_GUARD: self.update_node_layout() #-------------------------------------------------------------------------- # NodeItem API #-------------------------------------------------------------------------- def update_node_layout(self): self._guard |= NODE_UPDATE_LAYOUT_GUARD self.get_member('input_sockets_visible').reset(self) self.get_member('output_sockets_visible').reset(self) self.assign_socket_indices() self.height = self.compute_height() self.update_sockets_and_edges() self._guard &= ~NODE_UPDATE_LAYOUT_GUARD def assign_socket_indices(self): for socket in self.input_sockets: if socket in self.input_sockets_visible: socket.index = self.input_sockets_visible.index(socket) else: socket.index = 0 for socket in self.output_sockets: if socket in self.output_sockets_visible: socket.index = self.output_sockets_visible.index(socket) else: socket.index = 0 def update_sockets_and_edges(self): for socket in self.input_sockets_visible + self.output_sockets_visible: socket.update_sockets() for edge in socket.edges: edge.update_positions() def compute_height(self): socket_space = max(sum(s.socket_spacing for s in self.input_sockets_visible), sum(s.socket_spacing for s in self.output_sockets_visible)) return math.ceil(self.title_height + 2 * self.padding + 2 * self.edge_size + socket_space) # XXX must avoid cyclic updates .. def set_position(self, pos): self.position = pos self.proxy.set_position(pos) def getContentView(self): if not self.show_content_inline and self.content is not None: return self.content.content_objects[:] return []
class ColorDialog(ToolkitDialog): """ A toolkit dialog that allows the user to select a color. """ #: The currently selected color of the dialog. current_color = d_(ColorMember('white')) #: Whether or not to show the alpha value control. show_alpha = d_(Bool(True)) #: Whether or not to show the dialog ok/cancel buttons. show_buttons = d_(Bool(True)) #: The color selected when the user clicks accepts the dialog. #: This value is output only. selected_color = ColorMember() #: A reference to the ProxyColorDialog object. proxy = Typed(ProxyColorDialog) @staticmethod def get_color(parent=None, **kwargs): """ A static method which launches a color dialog. Parameters ---------- parent : ToolkitObject or None The parent toolkit object for this dialog. **kwargs Additional data to pass to the dialog constructor. Returns ------- result : Color or None The selected color or None if no color was selected. """ dialog = ColorDialog(parent, **kwargs) if dialog.exec_(): return dialog.selected_color @staticmethod def custom_count(): """ Get the number of available custom colors. The custom colors are shared among all color dialogs. Returns ------- result : int The number of available custom colors. Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: return proxy_cls.custom_count() return 0 @staticmethod def custom_color(index): """ Get the custom color for the given index. The custom colors are shared among all color dialogs. Parameters ---------- index : int The integer index of the custom color. Returns ------- result : Color The custom color for the index. Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: return proxy_cls.custom_color(index) return Color(255, 255, 255) @staticmethod def set_custom_color(index, color): """ Set the custom color for the given index. The custom colors are shared among all color dialogs. Parameters ---------- index : int The integer index of the custom color. color : Color The custom color to set for the index Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: proxy_cls.set_custom_color(index, color) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('current_color', 'show_alpha', 'show_buttons') def _update_proxy(self, change): """ An observer which updates the proxy when the data changes. """ # The superclass implementation is sufficient. super(ColorDialog, self)._update_proxy(change) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def _prepare(self): """ A reimplemented preparation method. This method resets the selected color to None. """ super(ColorDialog, self)._prepare() self.selected_color = None
class ViewerPlugin(Plugin): #: Background color background_mode = Enum('gradient', 'solid').tag(config=True) background_top = ColorMember('lightgrey').tag(config=True) background_bottom = ColorMember('grey').tag(config=True) trihedron_mode = Unicode('right-lower').tag(config=True) #: Exporters exporters = ContainerList() def get_viewers(self): ViewerDockItem = viewer_factory() dock = self.workbench.get_plugin('declaracad.ui').get_dock_area() for item in dock.dock_items(): if isinstance(item, ViewerDockItem): yield item def fit_all(self, event=None): return viewer = self.get_viewer() viewer.proxy.display.FitAll() def run(self, event=None): viewer = self.get_viewer() editor = self.workbench.get_plugin('declaracad.editor').get_editor() viewer.renderer.set_source(editor.get_text()) viewer.renderer.version += 1 def get_viewer(self, name=None): for viewer in self.get_viewers(): if name is None: return viewer elif viewer.name == name: return viewer def _default_exporters(self): """ TODO: push to an ExtensionPoint """ from .exporters.stl.exporter import StlExporter from .exporters.step.exporter import StepExporter return [StlExporter, StepExporter] def export(self, event): """ Export the current model to stl """ from twisted.internet import reactor options = event.parameters.get('options') if not options: raise ValueError("An export `options` parameter is required") # Pickle the configured exporter and send it over cmd = [sys.executable] if not sys.executable.endswith('declarcad'): cmd.append('main.py') data = jsonpickle.dumps(options) assert data != 'null', f"Exporter failed to serialize: {options}" cmd.extend(['export', data]) log.debug(" ".join(cmd)) protocol = ProcessLineReceiver() reactor.spawnProcess(protocol, sys.executable, args=cmd, env=os.environ) return protocol def screenshot(self, event): """ Export the views as a screenshot """ if 'options' not in event.parameters: editor = self.workbench.get_plugin('declaracad.editor') options = ScreenshotOptions(filename=editor.active_document.name) else: options = event.parameters.get('options') results = [] if options.target: viewer = self.get_viewer(options.target) if viewer: results.append(viewer.renderer.screenshot(options.path)) else: for i, viewer in enumerate(self.get_viewers()): # Insert view number path, ext = os.path.splitext(options.path) filename = "{}-{}{}".format(path, i + 1, ext) results.append(viewer.renderer.screenshot(filename)) return results
class Shape(ToolkitObject): """ Abstract shape component that can be displayed on the screen and represented by the framework. Notes ------ This shape's proxy holds an internal reference to the underlying shape which can be accessed using `self.proxy.shape` if needed. The topology of the shape can be accessed using the `self.proxy.topology` attribute. """ #: Reference to the implementation control proxy = Typed(ProxyShape) #: Set to true to prevent destruction of the shape when removed #: from the viewer. You need to manually manage it otherwise it'll #: create a memory leak cached = d_(Bool(False)) #: Whether the shape should be displayed and exported when in a part #: If set to false this shape will be excluded from the rendered part display = d_(Bool(True)) #: Description to display in the tooltip when clicked description = d_(Str()) #: The tolerance to use for operations that may require it. tolerance = d_(Float(strict=False)) def _default_tolerance(self): return settings.tolerance #: Material material = d_(Coerced(Material)) #: A string representing the color of the shape. color = d_(ColorMember()) #: The opacity of the shape used for display. transparency = d_(FloatRange(0.0, 1.0, 0.0)) #: Texture to apply to the shape texture = d_(Instance(Texture)) #: Position alias def _get_x(self): return self.position.x def _set_x(self, v): self.position.x = v x = d_(Property(_get_x, _set_x)) def _get_y(self): return self.position.y def _set_y(self, v): self.position.y = v y = d_(Property(_get_y, _set_y)) def _get_z(self): return self.position.z def _set_z(self, v): self.position.z = v z = d_(Property(_get_z, _set_z)) #: A tuple or list of the (x, y, z) position of this shape. This is #: coerced into a Point. position = d_(Coerced(Point, coercer=coerce_point)) def _default_position(self): return Point(0, 0, 0) #: A tuple or list of the (u, v, w) normal vector of this shape. This is #: coerced into a Vector setting the orentation of the shape. #: This is effectively the working plane. direction = d_(Coerced(Direction, coercer=coerce_direction)) def _default_direction(self): return Direction(0, 0, 1) #: Rotation about the normal vector in radians rotation = d_(Coerced(float, coercer=coerce_rotation)) def _get_axis(self): return (self.position, self.direction, self.rotation) def _set_axis(self, axis): self.position, self.direction, self.rotation = axis #: A tuple or list of the (u, v, w) axis of this shape. This is #: coerced into a Vector that defines the x, y, and z orientation of #: this shape. axis = d_(Property(_get_axis, _set_axis)) def _get_topology(self): return self.proxy.topology #: A read only property that accesses the topology of the shape such #: as edges, faces, shells, solids, etc.... topology = Property(_get_topology, cached=True) def _get_bounding_box(self): if self.proxy.shape: try: return self.proxy.get_bounding_box() except Exception as e: pass #: Bounding box of this shape bbox = Property(_get_bounding_box, cached=True) @observe('color', 'transparency', 'display', 'texture', 'position', 'direction') def _update_proxy(self, change): super()._update_proxy(change) @observe('proxy.shape') def _update_properties(self, change): """ Clear the cached references when the shape changes. """ for k in ('bbox', 'topology'): self.get_member(k).reset(self) self.constructed() #: Triggered when the shape is constructed constructed = d_(Event(), writable=False) def activate_proxy(self): """ Activate the proxy object tree. This method should be called by a node to activate the proxy tree by making two initialization passes over the tree, from this node downward. This method is automatically at the proper times and should not normally need to be invoked by user code. """ self.activate_top_down() for child in self.children: # Make sure each is initialized upon activation if not child.is_initialized: child.initialize() if isinstance(child, ToolkitObject): if not child.proxy_is_active: child.activate_proxy() # Generating the model can take a lot of time # so process events inbetween to keep the UI from freezing Application.instance().process_events() self.activate_bottom_up() self.proxy_is_active = True self.activated() def render(self): """ Generates and returns the actual shape from the declaration. Enaml does this automatically when it's included in the viewer so this is only neede when working with shapes manually. Returns ------- shape: TopoDS_Shape The shape generated by this declaration. """ if not self.is_initialized: self.initialize() if not self.proxy_is_active: self.activate_proxy() return self.proxy.shape
class Widget(ToolkitObject): """ The base class of visible widgets in Enaml. """ #: Whether or not the widget is enabled. enabled = d_(Bool(True)) #: Whether or not the widget is visible. visible = d_(Bool(True)) #: The background color of the widget. background = d_(ColorMember()) # TODO remove in Enaml version 0.8.0 bgcolor = _warn_prop('bgcolor', 'background') #: The foreground color of the widget. foreground = d_(ColorMember()) # TODO remove in Enaml version 0.8.0 fgcolor = _warn_prop('fgcolor', 'foreground') #: The font used for the widget. font = d_(FontMember()) #: The minimum size for the widget. The default means that the #: client should determine an intelligent minimum size. minimum_size = d_(Coerced(Size, (-1, -1))) #: The maximum size for the widget. The default means that the #: client should determine and inteliigent maximum size. maximum_size = d_(Coerced(Size, (-1, -1))) #: The tool tip to show when the user hovers over the widget. tool_tip = d_(Unicode()) #: The status tip to show when the user hovers over the widget. status_tip = d_(Unicode()) #: A flag indicating whether or not to show the focus rectangle for #: the given widget. This is not necessarily support by all widgets #: on all clients. A value of None indicates to use the default as #: supplied by the client. show_focus_rect = d_(Enum(None, True, False)) #: A reference to the ProxyWidget object. proxy = Typed(ProxyWidget) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe(('enabled', 'visible', 'background', 'foreground', 'font', 'minimum_size', 'maximum_size', 'show_focus_rect', 'tool_tip', 'status_tip')) def _update_proxy(self, change): """ Update the proxy widget when the Widget data changes. This method only updates the proxy when an attribute is updated; not when it is created or deleted. """ # The superclass implementation is sufficient. super(Widget, self)._update_proxy(change) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def show(self): """ Ensure the widget is shown. Calling this method will also set the widget visibility to True. """ self.visible = True if self.proxy_is_active: self.proxy.ensure_visible() def hide(self): """ Ensure the widget is hidden. Calling this method will also set the widget visibility to False. """ self.visible = False if self.proxy_is_active: self.proxy.ensure_hidden()
class EdgeItem(GraphicsItem): """ A edge-item in a node graph """ id = d_(Str()) name = d_(Unicode()) edge_type = d_(Typed(EdgeType)) line_width = d_(Float(2.0)) edge_roundness = d_(Int(100)) pos_source = d_(Typed(Point2D)) pos_destination = d_(Typed(Point2D)) color_default = d_(ColorMember("#001000FF")) color_selected = d_(ColorMember("#00ff00FF")) start_socket = ForwardInstance(import_node_socket) end_socket = ForwardInstance(import_node_socket) #: the model item from the underlying graph structure model = d_(Typed(Atom)) #: A reference to the ProxyComboBox object. proxy = Typed(ProxyEdgeItem) context_menu_event = d_(Event()) def _default_id(self): if self.scene is not None: cls = self.__class__ return self.scene.generate_item_id(cls.__name__, cls) return "<undefined>" def _default_name(self): return self.id def _default_pos_source(self): if self.start_socket is not None: return self.start_socket.absolute_position return Point2D(x=0, y=0) def _default_pos_destination(self): if self.end_socket is not None: return self.end_socket.absolute_position return Point2D(x=0, y=0) #-------------------------------------------------------------------------- # Content Handlers #-------------------------------------------------------------------------- def destroy(self): if self.scene is not None and self.scene.controller is not None: self.scene.controller.edge_disconnect(self.id) if self.start_socket and self in self.start_socket.edges: self.start_socket = None if self.end_socket and self in self.end_socket.edges: self.end_socket = None super(EdgeItem, self).destroy() #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('name', 'color_default', 'color_selected', 'line_width', 'edge_type', 'edge_roundness', 'pos_source', 'pos_destination') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(EdgeItem, self)._update_proxy(change) self.request_update() def _observe_start_socket(self, change): new_socket = change['value'] old_socket = change.get('oldvalue', None) if old_socket: if old_socket.parent is not None: old_socket.parent.unobserve('position', self.update_pos_source) else: log.warning('try to disconnect socket without parent', old_socket) if self in old_socket.edges: old_socket.edges.remove(self) if new_socket: if new_socket.parent is not None: self.pos_source = new_socket.parent.position + new_socket.relative_position new_socket.parent.observe('position', self.update_pos_source) else: log.warning('try to disconnect socket without parent: %s' % new_socket) new_socket.edges.append(self) def _observe_end_socket(self, change): new_socket = change['value'] old_socket = change.get('oldvalue', None) if old_socket: if old_socket.parent: old_socket.parent.unobserve('position', self.update_pos_destination) if self in old_socket.edges: old_socket.edges.remove(self) if new_socket: if new_socket.parent: self.pos_destination = new_socket.parent.position + new_socket.relative_position new_socket.parent.observe('position', self.update_pos_destination) else: log.warning("connected to socket without parent: %s" % new_socket) new_socket.edges.append(self) #-------------------------------------------------------------------------- # Protected API #-------------------------------------------------------------------------- def update_positions(self): if self.start_socket is not None: node_position = self.start_socket.parent.position self.pos_source = node_position + self.start_socket.relative_position if self.end_socket is not None: node_position = self.end_socket.parent.position self.pos_destination = node_position + self.end_socket.relative_position def update_pos_source(self, change): node_position = change['value'] if self.start_socket is not None: self.pos_source = node_position + self.start_socket.relative_position def update_pos_destination(self, change): node_position = change['value'] if self.end_socket is not None: self.pos_destination = node_position + self.end_socket.relative_position