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)
Exemple #2
0
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()
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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()
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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()
Exemple #9
0
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)
Exemple #10
0
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()
Exemple #11
0
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()
Exemple #12
0
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
Exemple #14
0
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
Exemple #15
0
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
Exemple #17
0
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
Exemple #18
0
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()
Exemple #19
0
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 []
Exemple #21
0
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
Exemple #22
0
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
Exemple #23
0
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
Exemple #24
0
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