예제 #1
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._theme:
            self._theme = Application.getInstance().getTheme()

        if not self._enabled_shader:
            self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
            self._enabled_shader.setUniformValue("u_overhangColor", Color(*self._theme.getColor("model_overhang").getRgb()))

        if not self._disabled_shader:
            self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
            self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*self._theme.getColor("model_unslicable").getRgb()))
            self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*self._theme.getColor("model_unslicable_alt").getRgb()))
            self._disabled_shader.setUniformValue("u_width", 50.0)

        if not self._non_printing_shader:
            self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
            self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb()))
            self._non_printing_shader.setUniformValue("u_opacity", 0.6)

        global_container_stack = Application.getInstance().getGlobalContainerStack()
        if global_container_stack:
            support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
            support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)

            if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
                angle = support_angle_stack.getProperty("support_angle", "value")
                # Make sure the overhang angle is valid before passing it to the shader
                # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
                if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]:
                    self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
                else:
                    self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
            else:
                self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))

        for node in DepthFirstIterator(scene.getRoot()):
            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    uniforms = {}
                    shade_factor = 1.0

                    per_mesh_stack = node.callDecoration("getStack")

                    # Get color to render this mesh in from ExtrudersModel
                    extruder_index = 0
                    extruder_id = node.callDecoration("getActiveExtruder")
                    if extruder_id:
                        extruder_index = max(0, self._extruders_model.find("id", extruder_id))

                    # Use the support extruder instead of the active extruder if this is a support_mesh
                    if per_mesh_stack:
                        if per_mesh_stack.getProperty("support_mesh", "value"):
                            extruder_index = int(global_container_stack.getProperty("support_extruder_nr", "value"))

                    try:
                        material_color = self._extruders_model.getItem(extruder_index)["color"]
                    except KeyError:
                        material_color = self._extruders_model.defaultColors[0]

                    if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
                        # Shade objects that are printed with the non-active extruder 25% darker
                        shade_factor = 0.6

                    try:
                        # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
                        # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
                        uniforms["diffuse_color"] = [
                            shade_factor * int(material_color[1:3], 16) / 255,
                            shade_factor * int(material_color[3:5], 16) / 255,
                            shade_factor * int(material_color[5:7], 16) / 255,
                            1.0
                        ]
                    except ValueError:
                        pass

                    if node.callDecoration("isNonPrintingMesh"):
                        if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")):
                            renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True)
                        else:
                            renderer.queueNode(node, shader = self._non_printing_shader, transparent = True)
                    elif getattr(node, "_outside_buildarea", False):
                        renderer.queueNode(node, shader = self._disabled_shader)
                    else:
                        renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
                if node.callDecoration("isGroup") and Selection.isSelected(node):
                    renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop)
예제 #2
0
    def event(self, event):
        if event.type == Event.ViewActivateEvent:
            # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
            # This can happen when you do the following steps:
            #   1. Start Cura
            #   2. Load a model
            #   3. Switch to Custom mode
            #   4. Select the model and click on the per-object tool icon
            #   5. Switch view to Layer view or X-Ray
            #   6. Cura will very likely crash
            # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
            # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
            # context is None.
            if Platform.isOSX():
                if QOpenGLContext.currentContext() is None:
                    Logger.log(
                        "d",
                        "current context of OpenGL is empty on Mac OS X, will try to create shaders later"
                    )
                    CuraApplication.getInstance().callLater(
                        lambda e=event: self.event(e))
                    return

            if not self._xray_pass:
                # Currently the RenderPass constructor requires a size > 0
                # This should be fixed in RenderPass's constructor.
                self._xray_pass = XRayPass.XRayPass(1, 1)

            self.getRenderer().addRenderPass(self._xray_pass)

            if not self._xray_composite_shader:
                self._xray_composite_shader = OpenGL.getInstance(
                ).createShaderProgram(
                    Resources.getPath(Resources.Shaders,
                                      "xray_composite.shader"))
                theme = Application.getInstance().getTheme()
                self._xray_composite_shader.setUniformValue(
                    "u_background_color",
                    Color(*theme.getColor("viewport_background").getRgb()))
                self._xray_composite_shader.setUniformValue(
                    "u_outline_color",
                    Color(*theme.getColor("model_selection_outline").getRgb()))
                self._xray_composite_shader.setUniformValue(
                    "u_flat_error_color_mix",
                    1.)  # Show flat error color _only_ in xray-view.

            if not self._composite_pass:
                self._composite_pass = self.getRenderer().getRenderPass(
                    "composite")

            self._old_layer_bindings = self._composite_pass.getLayerBindings()
            self._composite_pass.setLayerBindings(
                ["default", "selection", "xray"])
            self._old_composite_shader = self._composite_pass.getCompositeShader(
            )
            self._composite_pass.setCompositeShader(
                self._xray_composite_shader)

        if event.type == Event.ViewDeactivateEvent:
            self.getRenderer().removeRenderPass(self._xray_pass)
            self._composite_pass.setLayerBindings(self._old_layer_bindings)
            self._composite_pass.setCompositeShader(self._old_composite_shader)
예제 #3
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._selection_shader:
            self._selection_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "default.shader"))
            self._selection_shader.setUniformValue("u_color",
                                                   Color(32, 32, 32, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(
                    node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node,
                                           transparent=True,
                                           shader=self._selection_shader)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node,
                                           mesh=layer_data,
                                           mode=RenderBatch.RenderMode.Lines,
                                           range=(start, end))

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(
                                    layer).createMesh()
                                if not layer_mesh or layer_mesh.getVertices(
                                ) is None:
                                    continue
                            except:
                                continue
                            if self._current_layer_mesh:  #Threading thing; Switching between views can cause the current layer mesh to be deleted.
                                self._current_layer_mesh.addVertices(
                                    layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            if self._current_layer_mesh:
                                self._current_layer_mesh.addColors(
                                    layer_mesh.getColors() * brightness)
                    if self._current_layer_mesh:
                        renderer.queueNode(node, mesh=self._current_layer_mesh)

                    if not self._current_layer_jumps:
                        self._current_layer_jumps = MeshData()
                        for i in range(1):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(
                                    layer).createJumps()
                                if not layer_mesh or layer_mesh.getVertices(
                                ) is None:
                                    continue
                            except:
                                continue

                            self._current_layer_jumps.addVertices(
                                layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_jumps.addColors(
                                layer_mesh.getColors() * brightness)

                    renderer.queueNode(node, mesh=self._current_layer_jumps)
예제 #4
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._enabled_material:
            if Preferences.getInstance().getValue("view/show_overhang"):
                self._enabled_material = renderer.createMaterial(
                    Resources.getPath(Resources.Shaders, "default.vert"),
                    Resources.getPath(Resources.Shaders, "overhang.frag"))
            else:
                self._enabled_material = renderer.createMaterial(
                    Resources.getPath(Resources.Shaders, "default.vert"),
                    Resources.getPath(Resources.Shaders, "default.frag"))

            self._enabled_material.setUniformValue("u_ambientColor",
                                                   Color(0.3, 0.3, 0.3, 1.0))
            self._enabled_material.setUniformValue("u_diffuseColor",
                                                   self.EnabledColor)
            self._enabled_material.setUniformValue("u_specularColor",
                                                   Color(0.4, 0.4, 0.4, 1.0))
            self._enabled_material.setUniformValue("u_overhangColor",
                                                   Color(1.0, 0.0, 0.0, 1.0))
            self._enabled_material.setUniformValue("u_shininess", 20.)

        if not self._disabled_material:
            self._disabled_material = renderer.createMaterial(
                Resources.getPath(Resources.Shaders, "default.vert"),
                Resources.getPath(Resources.Shaders, "default.frag"))
            self._disabled_material.setUniformValue("u_ambientColor",
                                                    Color(0.3, 0.3, 0.3, 1.0))
            self._disabled_material.setUniformValue("u_diffuseColor",
                                                    self.DisabledColor)
            self._disabled_material.setUniformValue("u_specularColor",
                                                    Color(0.4, 0.4, 0.4, 1.0))
            self._disabled_material.setUniformValue("u_overhangColor",
                                                    Color(1.0, 0.0, 0.0, 1.0))
            self._disabled_material.setUniformValue("u_shininess", 20.)

        if Application.getInstance().getMachineManager().getActiveProfile():
            profile = Application.getInstance().getMachineManager(
            ).getActiveProfile()

            if profile.getSettingValue("support_enable"):
                angle = profile.getSettingValue("support_angle")
                if angle != None:
                    self._enabled_material.setUniformValue(
                        "u_overhangAngle", math.cos(math.radians(90 - angle)))
            else:
                self._enabled_material.setUniformValue(
                    "u_overhangAngle", math.cos(math.radians(0)))

        for node in DepthFirstIterator(scene.getRoot()):
            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    # TODO: Find a better way to handle this
                    #if node.getBoundingBoxMesh():
                    #    renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines)
                    if hasattr(node, "_outside_buildarea"):
                        if node._outside_buildarea:
                            renderer.queueNode(
                                node, material=self._disabled_material)
                        else:
                            renderer.queueNode(node,
                                               material=self._enabled_material)
                    else:
                        renderer.queueNode(node,
                                           material=self._enabled_material)
                if node.callDecoration("isGroup"):
                    renderer.queueNode(scene.getRoot(),
                                       mesh=node.getBoundingBoxMesh(),
                                       mode=Renderer.RenderLines)
예제 #5
0
 def _dropAlpha(self, color):
     return Color(color.r, color.g, color.b, 0.0)
예제 #6
0
파일: BuildVolume.py 프로젝트: won21kr/Cura
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        # Outline 'cube' of the build volume
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(max_w, min_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        self.setMeshData(mb.build())

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))

        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(
                        points[:, 1]
                ) >= 0:  # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(
                        numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        self._volume_aabb = AxisAlignedBox(
            minimum=Vector(min_w, min_h - 1.0, min_d),
            maximum=Vector(max_w, max_h - self._raft_thickness, max_d))

        bed_adhesion_size = 0.0

        container_stack = Application.getInstance().getGlobalContainerStack()
        if container_stack:
            bed_adhesion_size = self._getBedAdhesionSize(container_stack)

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(
                min_w + bed_adhesion_size + 1, min_h,
                min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum=Vector(
                max_w - bed_adhesion_size - 1, max_h - self._raft_thickness,
                max_d - disallowed_area_size + bed_adhesion_size - 1))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds
예제 #7
0
    def event(self, event) -> bool:
        modifiers = QApplication.keyboardModifiers()
        ctrl_is_active = modifiers & Qt.ControlModifier
        shift_is_active = modifiers & Qt.ShiftModifier
        if event.type == Event.KeyPressEvent and ctrl_is_active:
            amount = 10 if shift_is_active else 1
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + amount)
                return True
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - amount)
                return True

        if event.type == Event.ViewActivateEvent:
            # Start listening to changes.
            Application.getInstance().getPreferences(
            ).preferenceChanged.connect(self._onPreferencesChanged)
            self._controller.getScene().getRoot().childrenChanged.connect(
                self._onSceneChanged)

            self.calculateColorSchemeLimits()
            self.calculateMaxLayers()
            self.calculateMaxPathsOnLayer(self._current_layer_num)

            # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
            # This can happen when you do the following steps:
            #   1. Start Cura
            #   2. Load a model
            #   3. Switch to Custom mode
            #   4. Select the model and click on the per-object tool icon
            #   5. Switch view to Layer view or X-Ray
            #   6. Cura will very likely crash
            # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
            # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
            # context is None.
            if Platform.isOSX():
                if QOpenGLContext.currentContext() is None:
                    Logger.log(
                        "d",
                        "current context of OpenGL is empty on Mac OS X, will try to create shaders later"
                    )
                    CuraApplication.getInstance().callLater(
                        lambda e=event: self.event(e))
                    return False

            # Make sure the SimulationPass is created
            layer_pass = self.getSimulationPass()
            renderer = self.getRenderer()
            if renderer is None:
                return False

            renderer.addRenderPass(layer_pass)

            # Make sure the NozzleNode is add to the root
            nozzle = self.getNozzleNode()
            nozzle.setParent(self.getController().getScene().getRoot())
            nozzle.setVisible(False)

            Application.getInstance().globalContainerStackChanged.connect(
                self._onGlobalStackChanged)
            self._onGlobalStackChanged()

            if not self._simulationview_composite_shader:
                plugin_path = cast(
                    str,
                    PluginRegistry.getInstance().getPluginPath(
                        "SimulationView"))
                self._simulationview_composite_shader = OpenGL.getInstance(
                ).createShaderProgram(
                    os.path.join(plugin_path,
                                 "simulationview_composite.shader"))
                theme = CuraApplication.getInstance().getTheme()
                if theme is not None:
                    self._simulationview_composite_shader.setUniformValue(
                        "u_background_color",
                        Color(*theme.getColor("viewport_background").getRgb()))
                    self._simulationview_composite_shader.setUniformValue(
                        "u_outline_color",
                        Color(*theme.getColor(
                            "model_selection_outline").getRgb()))

            if not self._composite_pass:
                self._composite_pass = cast(
                    CompositePass, renderer.getRenderPass("composite"))

            self._old_layer_bindings = self._composite_pass.getLayerBindings(
            )[:]  # make a copy so we can restore to it later
            self._composite_pass.getLayerBindings().append("simulationview")
            self._old_composite_shader = self._composite_pass.getCompositeShader(
            )
            self._composite_pass.setCompositeShader(
                self._simulationview_composite_shader)
            self._updateSliceWarningVisibility()

        elif event.type == Event.ViewDeactivateEvent:
            self._controller.getScene().getRoot().childrenChanged.disconnect(
                self._onSceneChanged)
            Application.getInstance().getPreferences(
            ).preferenceChanged.disconnect(self._onPreferencesChanged)
            self._wireprint_warning_message.hide()
            self._slice_first_warning_message.hide()
            Application.getInstance().globalContainerStackChanged.disconnect(
                self._onGlobalStackChanged)
            if self._global_container_stack:
                self._global_container_stack.propertyChanged.disconnect(
                    self._onPropertyChanged)
            if self._nozzle_node:
                self._nozzle_node.setParent(None)

            renderer = self.getRenderer()
            if renderer is None:
                return False

            if self._layer_pass is not None:
                renderer.removeRenderPass(self._layer_pass)
            if self._composite_pass:
                self._composite_pass.setLayerBindings(
                    cast(List[str], self._old_layer_bindings))
                self._composite_pass.setCompositeShader(
                    cast(ShaderProgram, self._old_composite_shader))

        return False
예제 #8
0
class LayerPolygon:
    NoneType = 0
    Inset0Type = 1
    InsetXType = 2
    SkinType = 3
    SupportType = 4
    SkirtType = 5
    InfillType = 6
    SupportInfillType = 7
    MoveCombingType = 8
    MoveRetractionType = 9

    __jump_map = numpy.logical_or(
        numpy.arange(10) == NoneType,
        numpy.arange(10) >= MoveCombingType)

    def __init__(self, mesh, extruder, line_types, data, line_widths):
        self._mesh = mesh
        self._extruder = extruder
        self._types = line_types
        self._data = data
        self._line_widths = line_widths

        self._vertex_begin = 0
        self._vertex_end = 0
        self._index_begin = 0
        self._index_end = 0

        self._jump_mask = self.__jump_map[self._types]
        self._jump_count = numpy.sum(self._jump_mask)
        self._mesh_line_count = len(self._types) - self._jump_count
        self._vertex_count = self._mesh_line_count + numpy.sum(
            self._types[1:] == self._types[:-1])

        # Buffering the colors shouldn't be necessary as it is not
        # re-used and can save alot of memory usage.
        self._colors = self.__color_map[self._types]
        self._color_map = self.__color_map

        # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
        # Should be generated in better way, not hardcoded.
        self._isInfillOrSkinTypeMap = numpy.array(
            [0, 0, 0, 1, 0, 0, 1, 1, 0, 0], dtype=numpy.bool)

        self._build_cache_line_mesh_mask = None
        self._build_cache_needed_points = None

    def buildCache(self):
        # For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
        self._build_cache_line_mesh_mask = numpy.logical_not(
            numpy.logical_or(self._jump_mask,
                             self._types == LayerPolygon.InfillType))
        mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
        self._index_begin = 0
        self._index_end = mesh_line_count

        self._build_cache_needed_points = numpy.ones((len(self._types), 2),
                                                     dtype=numpy.bool)
        # Only if the type of line segment changes do we need to add an extra vertex to change colors
        self._build_cache_needed_points[
            1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
        # Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
        numpy.logical_and(self._build_cache_needed_points,
                          self._build_cache_line_mesh_mask,
                          self._build_cache_needed_points)

        self._vertex_begin = 0
        self._vertex_end = numpy.sum(self._build_cache_needed_points)

    def build(self, vertex_offset, index_offset, vertices, colors, indices):
        if (self._build_cache_line_mesh_mask is
                None) or (self._build_cache_needed_points is None):
            self.buildCache()

        line_mesh_mask = self._build_cache_line_mesh_mask
        needed_points_list = self._build_cache_needed_points

        # Index to the points we need to represent the line mesh. This is constructed by generating simple
        # start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
        # Then then the indices for the points we don't need are thrown away based on the pre-calculated list.
        index_list = (numpy.arange(len(self._types)).reshape(
            (-1, 1)) + numpy.array([[0, 1]])).reshape(
                (-1, 1))[needed_points_list.reshape((-1, 1))]

        # The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
        self._vertex_begin += vertex_offset
        self._vertex_end += vertex_offset

        # Points are picked based on the index list to get the vertices needed.
        vertices[self._vertex_begin:self._vertex_end, :] = self._data[
            index_list, :]
        # Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
        colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(
            self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
        colors[self._vertex_begin:self._vertex_end, :] *= numpy.array(
            [[0.5, 0.5, 0.5, 1.0]], numpy.float32)

        # The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
        self._index_begin += index_offset
        self._index_end += index_offset

        indices[self._index_begin:self._index_end, :] = numpy.arange(
            self._index_end - self._index_begin, dtype=numpy.int32).reshape(
                (-1, 1))
        # When the line type changes the index needs to be increased by 2.
        indices[self._index_begin:self._index_end, :] += numpy.cumsum(
            needed_points_list[line_mesh_mask.ravel(),
                               0], dtype=numpy.int32).reshape((-1, 1))
        # Each line segment goes from it's starting point p to p+1, offset by the vertex index.
        # The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
        indices[self._index_begin:self._index_end, :] += numpy.array(
            [self._vertex_begin - 1, self._vertex_begin])

        self._build_cache_line_mesh_mask = None
        self._build_cache_needed_points = None

    def getColors(self):
        return self._colors

    def mapLineTypeToColor(self, line_types):
        return self._color_map[line_types]

    def isInfillOrSkinType(self, line_types):
        return self._isInfillOrSkinTypeMap[line_types]

    def lineMeshVertexCount(self):
        return (self._vertex_end - self._vertex_begin)

    def lineMeshElementCount(self):
        return (self._index_end - self._index_begin)

    @property
    def extruder(self):
        return self._extruder

    @property
    def types(self):
        return self._types

    @property
    def data(self):
        return self._data

    @property
    def elementCount(self):
        return (
            self._index_end - self._index_begin
        ) * 2  # The range of vertices multiplied by 2 since each vertex is used twice

    @property
    def lineWidths(self):
        return self._line_widths

    @property
    def jumpMask(self):
        return self._jump_mask

    @property
    def meshLineCount(self):
        return self._mesh_line_count

    @property
    def jumpCount(self):
        return self._jump_count

    # Calculate normals for the entire polygon using numpy.
    def getNormals(self):
        normals = numpy.copy(self._data)
        normals[:, 1] = 0.0  # We are only interested in 2D normals

        # Calculate the edges between points.
        # The call to numpy.roll shifts the entire array by one so that
        # we end up subtracting each next point from the current, wrapping
        # around. This gives us the edges from the next point to the current
        # point.
        normals = numpy.diff(normals, 1, 0)

        # Calculate the length of each edge using standard Pythagoras
        lengths = numpy.sqrt(normals[:, 0]**2 + normals[:, 2]**2)
        # The normal of a 2D vector is equal to its x and y coordinates swapped
        # and then x inverted. This code does that.
        normals[:, [0, 2]] = normals[:, [2, 0]]
        normals[:, 0] *= -1

        # Normalize the normals.
        normals[:, 0] /= lengths
        normals[:, 2] /= lengths

        return normals

    __color_mapping = {
        NoneType: Color(1.0, 1.0, 1.0, 1.0),
        Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
        InsetXType: Color(0.0, 1.0, 0.0, 1.0),
        SkinType: Color(1.0, 1.0, 0.0, 1.0),
        SupportType: Color(0.0, 1.0, 1.0, 1.0),
        SkirtType: Color(0.0, 1.0, 1.0, 1.0),
        InfillType: Color(1.0, 0.74, 0.0, 1.0),
        SupportInfillType: Color(0.0, 1.0, 1.0, 1.0),
        MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
        MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
    }

    # Should be generated in better way, not hardcoded.
    __color_map = numpy.array([[1.0, 1.0, 1.0, 1.0], [1.0, 0.0, 0.0, 1.0],
                               [0.0, 1.0, 0.0, 1.0], [1.0, 1.0, 0.0, 1.0],
                               [0.0, 1.0, 1.0, 1.0], [0.0, 1.0, 1.0, 1.0],
                               [1.0, 0.74, 0.0, 1.0], [0.0, 1.0, 1.0, 1.0],
                               [0.0, 0.0, 1.0, 1.0], [0.5, 0.5, 1.0, 1.0]])
class ToolHandle(SceneNode.SceneNode):
    NoAxis = 1
    XAxis = 2
    YAxis = 3
    ZAxis = 4
    AllAxis = 5

    # These colors are used to draw the selection pass only. They must be unique, which is
    # why we cannot rely on themed colors
    DisabledSelectionColor = Color(0.5, 0.5, 0.5, 1.0)
    XAxisSelectionColor = Color(1.0, 0.0, 0.0, 1.0)
    YAxisSelectionColor = Color(0.0, 0.0, 1.0, 1.0)
    ZAxisSelectionColor = Color(0.0, 1.0, 0.0, 1.0)
    AllAxisSelectionColor = Color(1.0, 1.0, 1.0, 1.0)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._disabled_axis_color = None
        self._x_axis_color = None
        self._y_axis_color = None
        self._z_axis_color = None
        self._all_axis_color = None

        self._axis_color_map = {}

        self._scene = Application.getInstance().getController().getScene()

        self._solid_mesh = None
        self._line_mesh = None
        self._selection_mesh = None
        self._shader = None

        self._previous_dist = None
        self._active_axis = None
        self._auto_scale = True

        self.setCalculateBoundingBox(False)

        Selection.selectionCenterChanged.connect(
            self._onSelectionCenterChanged)
        Application.getInstance().engineCreatedSignal.connect(
            self._onEngineCreated)

    def getLineMesh(self):
        return self._line_mesh

    def setLineMesh(self, mesh):
        self._line_mesh = mesh
        self.meshDataChanged.emit(self)

    def getSolidMesh(self):
        return self._solid_mesh

    def setSolidMesh(self, mesh):
        self._solid_mesh = mesh
        self.meshDataChanged.emit(self)

    def getSelectionMesh(self):
        return self._selection_mesh

    def setSelectionMesh(self, mesh):
        self._selection_mesh = mesh
        self.meshDataChanged.emit(self)

    def getMaterial(self):
        return self._shader

    def render(self, renderer):
        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "toolhandle.shader"))

        if self._auto_scale:
            camera_position = self._scene.getActiveCamera().getWorldPosition()
            dist = (camera_position - self.getWorldPosition()).length()
            scale = dist / 400
            self.setScale(Vector(scale, scale, scale))

        if self._line_mesh:
            renderer.queueNode(self,
                               mesh=self._line_mesh,
                               mode=RenderBatch.RenderMode.Lines,
                               overlay=True,
                               shader=self._shader)
        if self._solid_mesh:
            renderer.queueNode(self,
                               mesh=self._solid_mesh,
                               overlay=True,
                               shader=self._shader)

        return True

    def setActiveAxis(self, axis):
        if axis == self._active_axis or not self._shader:
            return

        if axis:
            self._shader.setUniformValue("u_activeColor",
                                         self._axis_color_map[axis])
        else:
            self._shader.setUniformValue("u_activeColor",
                                         self._disabled_axis_color)
        self._active_axis = axis
        self._scene.sceneChanged.emit(self)

    def isAxis(self, value):
        return value in self._axis_color_map

    def buildMesh(self):
        # This method should be overridden by toolhandle implementations
        pass

    def _onSelectionCenterChanged(self):
        self.setPosition(Selection.getSelectionCenter())

    def _onEngineCreated(self):
        theme = Application.getInstance().getTheme()
        self._disabled_axis_color = Color(
            *theme.getColor("disabled_axis").getRgb())
        self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
        self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
        self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
        self._all_axis_color = Color(*theme.getColor("all_axis").getRgb())

        self._axis_color_map = {
            self.NoAxis: self._disabled_axis_color,
            self.XAxis: self._x_axis_color,
            self.YAxis: self._y_axis_color,
            self.ZAxis: self._z_axis_color,
            self.AllAxis: self._all_axis_color
        }

        self.buildMesh()
예제 #10
0
class BuildVolume(SceneNode):
    VolumeOutlineColor = Color(12, 169, 227, 255)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self.setCalculateBoundingBox(False)

        self._active_profile = None
        self._active_instance = None
        Application.getInstance().getMachineManager(
        ).activeMachineInstanceChanged.connect(self._onActiveInstanceChanged)
        self._onActiveInstanceChanged()

        Application.getInstance().getMachineManager(
        ).activeProfileChanged.connect(self._onActiveProfileChanged)
        self._onActiveProfileChanged()

    def setWidth(self, width):
        if width: self._width = width

    def setHeight(self, height):
        if height: self._height = height

    def setDepth(self, depth):
        if depth: self._depth = depth

    def getDisallowedAreas(self):
        return self._disallowed_areas

    def setDisallowedAreas(self, areas):
        self._disallowed_areas = areas

    def render(self, renderer):
        if not self.getMeshData():
            return True

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "default.shader"))
            self._grid_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "grid.shader"))

        renderer.queueNode(self, mode=RenderBatch.RenderMode.Lines)
        renderer.queueNode(self,
                           mesh=self._grid_mesh,
                           shader=self._grid_shader,
                           backface_cull=True)
        if self._disallowed_area_mesh:
            renderer.queueNode(self,
                               mesh=self._disallowed_area_mesh,
                               shader=self._shader,
                               transparent=True,
                               backface_cull=True,
                               sort=-9)
        return True

    def rebuild(self):
        if self._width == 0 or self._height == 0 or self._depth == 0:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(max_w, min_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        self.setMeshData(mb.getData())

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))
        self._grid_mesh = mb.getData()
        for n in range(0, 6):
            v = self._grid_mesh.getVertex(n)
            self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2])

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds
                size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.getData()
        else:
            self._disallowed_area_mesh = None

        self._aabb = AxisAlignedBox(minimum=Vector(min_w, min_h - 1.0, min_d),
                                    maximum=Vector(max_w, max_h, max_d))

        skirt_size = 0.0

        profile = Application.getInstance().getMachineManager(
        ).getActiveProfile()
        if profile:
            skirt_size = self._getSkirtSize(profile)

        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(min_w + skirt_size, min_h,
                           min_d + skirt_size + disallowed_area_size),
            maximum=Vector(max_w - skirt_size, max_h,
                           max_d - skirt_size - disallowed_area_size))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds

    def _onActiveInstanceChanged(self):
        self._active_instance = Application.getInstance().getMachineManager(
        ).getActiveMachineInstance()

        if self._active_instance:
            self._width = self._active_instance.getMachineSettingValue(
                "machine_width")
            self._height = self._active_instance.getMachineSettingValue(
                "machine_height")
            self._depth = self._active_instance.getMachineSettingValue(
                "machine_depth")

            self._updateDisallowedAreas()

            self.rebuild()

    def _onActiveProfileChanged(self):
        if self._active_profile:
            self._active_profile.settingValueChanged.disconnect(
                self._onSettingValueChanged)

        self._active_profile = Application.getInstance().getMachineManager(
        ).getActiveProfile()
        if self._active_profile:
            self._active_profile.settingValueChanged.connect(
                self._onSettingValueChanged)
            self._updateDisallowedAreas()
            self.rebuild()

    def _onSettingValueChanged(self, setting):
        if setting in self._skirt_settings:
            self._updateDisallowedAreas()
            self.rebuild()

    def _updateDisallowedAreas(self):
        if not self._active_instance or not self._active_profile:
            return

        disallowed_areas = self._active_instance.getMachineSettingValue(
            "machine_disallowed_areas")
        areas = []

        skirt_size = 0.0
        if self._active_profile:
            skirt_size = self._getSkirtSize(self._active_profile)

        if disallowed_areas:
            for area in disallowed_areas:
                poly = Polygon(numpy.array(area, numpy.float32))
                poly = poly.getMinkowskiHull(
                    Polygon(
                        numpy.array(
                            [[-skirt_size, 0],
                             [-skirt_size * 0.707, skirt_size * 0.707],
                             [0, skirt_size],
                             [skirt_size * 0.707, skirt_size * 0.707],
                             [skirt_size, 0],
                             [skirt_size * 0.707, -skirt_size * 0.707],
                             [0, -skirt_size],
                             [-skirt_size * 0.707, -skirt_size * 0.707]],
                            numpy.float32)))

                areas.append(poly)

        if skirt_size > 0:
            half_machine_width = self._active_instance.getMachineSettingValue(
                "machine_width") / 2
            half_machine_depth = self._active_instance.getMachineSettingValue(
                "machine_depth") / 2

            areas.append(
                Polygon(
                    numpy.array([[-half_machine_width, -half_machine_depth],
                                 [-half_machine_width, half_machine_depth],
                                 [
                                     -half_machine_width + skirt_size,
                                     half_machine_depth - skirt_size
                                 ],
                                 [
                                     -half_machine_width + skirt_size,
                                     -half_machine_depth + skirt_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[half_machine_width, half_machine_depth],
                                 [half_machine_width, -half_machine_depth],
                                 [
                                     half_machine_width - skirt_size,
                                     -half_machine_depth + skirt_size
                                 ],
                                 [
                                     half_machine_width - skirt_size,
                                     half_machine_depth - skirt_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[-half_machine_width, half_machine_depth],
                                 [half_machine_width, half_machine_depth],
                                 [
                                     half_machine_width - skirt_size,
                                     half_machine_depth - skirt_size
                                 ],
                                 [
                                     -half_machine_width + skirt_size,
                                     half_machine_depth - skirt_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[half_machine_width, -half_machine_depth],
                                 [-half_machine_width, -half_machine_depth],
                                 [
                                     -half_machine_width + skirt_size,
                                     -half_machine_depth + skirt_size
                                 ],
                                 [
                                     half_machine_width - skirt_size,
                                     -half_machine_depth + skirt_size
                                 ]], numpy.float32)))

        self._disallowed_areas = areas

    def _getSkirtSize(self, profile):
        skirt_size = 0.0

        adhesion_type = profile.getSettingValue("adhesion_type")
        if adhesion_type == "skirt":
            skirt_distance = profile.getSettingValue("skirt_gap")
            skirt_line_count = profile.getSettingValue("skirt_line_count")
            skirt_size = skirt_distance + (
                skirt_line_count * profile.getSettingValue("skirt_line_width"))
        elif adhesion_type == "brim":
            skirt_size = profile.getSettingValue("brim_width")
        elif adhesion_type == "raft":
            skirt_size = profile.getSettingValue("raft_margin")

        if profile.getSettingValue("draft_shield_enabled"):
            skirt_size += profile.getSettingValue("draft_shield_dist")

        skirt_size += profile.getSettingValue("xy_offset")

        return skirt_size

    def _clamp(self, value, min_value, max_value):
        return max(min(value, max_value), min_value)

    _skirt_settings = [
        "adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width",
        "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled",
        "draft_shield_dist", "xy_offset"
    ]
예제 #11
0
    def rebuild(self):
        if self._width == 0 or self._height == 0 or self._depth == 0:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(max_w, min_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        self.setMeshData(mb.getData())

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))
        self._grid_mesh = mb.getData()
        for n in range(0, 6):
            v = self._grid_mesh.getVertex(n)
            self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2])

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds
                size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.getData()
        else:
            self._disallowed_area_mesh = None

        self._aabb = AxisAlignedBox(minimum=Vector(min_w, min_h - 1.0, min_d),
                                    maximum=Vector(max_w, max_h, max_d))

        skirt_size = 0.0

        profile = Application.getInstance().getMachineManager(
        ).getActiveProfile()
        if profile:
            skirt_size = self._getSkirtSize(profile)

        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(min_w + skirt_size, min_h,
                           min_d + skirt_size + disallowed_area_size),
            maximum=Vector(max_w - skirt_size, max_h,
                           max_d - skirt_size - disallowed_area_size))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds
예제 #12
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._extruders_model:
            self._extruders_model = Application.getInstance(
            ).getExtrudersModel()

        if not self._theme:
            self._theme = Application.getInstance().getTheme()

        if not self._enabled_shader:
            self._enabled_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "overhang.shader"))
            self._enabled_shader.setUniformValue(
                "u_overhangColor",
                Color(*self._theme.getColor("model_overhang").getRgb()))

        if not self._disabled_shader:
            self._disabled_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "striped.shader"))
            self._disabled_shader.setUniformValue(
                "u_diffuseColor1",
                Color(*self._theme.getColor("model_unslicable").getRgb()))
            self._disabled_shader.setUniformValue(
                "u_diffuseColor2",
                Color(*self._theme.getColor("model_unslicable_alt").getRgb()))
            self._disabled_shader.setUniformValue("u_width", 50.0)

        if not self._non_printing_shader:
            self._non_printing_shader = OpenGL.getInstance(
            ).createShaderProgram(
                Resources.getPath(Resources.Shaders,
                                  "transparent_object.shader"))
            self._non_printing_shader.setUniformValue(
                "u_diffuseColor",
                Color(*self._theme.getColor("model_non_printing").getRgb()))
            self._non_printing_shader.setUniformValue("u_opacity", 0.6)

        if not self._support_mesh_shader:
            self._support_mesh_shader = OpenGL.getInstance(
            ).createShaderProgram(
                Resources.getPath(Resources.Shaders, "striped.shader"))
            self._support_mesh_shader.setUniformValue("u_vertical_stripes",
                                                      True)
            self._support_mesh_shader.setUniformValue("u_width", 5.0)

        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if global_container_stack:
            if Application.getInstance().getPreferences().getValue(
                    "view/show_overhang"):
                # Make sure the overhang angle is valid before passing it to the shader
                if self._support_angle is not None and self._support_angle >= 0 and self._support_angle <= 90:
                    self._enabled_shader.setUniformValue(
                        "u_overhangAngle",
                        math.cos(math.radians(90 - self._support_angle)))
                else:
                    self._enabled_shader.setUniformValue(
                        "u_overhangAngle", math.cos(math.radians(0))
                    )  #Overhang angle of 0 causes no area at all to be marked as overhang.
            else:
                self._enabled_shader.setUniformValue("u_overhangAngle",
                                                     math.cos(math.radians(0)))

        for node in DepthFirstIterator(scene.getRoot()):
            if not node.render(renderer):
                if node.getMeshData() and node.isVisible(
                ) and not node.callDecoration("getLayerData"):
                    uniforms = {}
                    shade_factor = 1.0

                    per_mesh_stack = node.callDecoration("getStack")

                    extruder_index = node.callDecoration(
                        "getActiveExtruderPosition")
                    if extruder_index is None:
                        extruder_index = "0"
                    extruder_index = int(extruder_index)

                    # Use the support extruder instead of the active extruder if this is a support_mesh
                    if per_mesh_stack:
                        if per_mesh_stack.getProperty("support_mesh", "value"):
                            extruder_index = int(
                                global_container_stack.
                                getExtruderPositionValueWithDefault(
                                    "support_extruder_nr"))

                    try:
                        material_color = self._extruders_model.getItem(
                            extruder_index)["color"]
                    except KeyError:
                        material_color = self._extruders_model.defaultColors[0]

                    if extruder_index != ExtruderManager.getInstance(
                    ).activeExtruderIndex:
                        # Shade objects that are printed with the non-active extruder 25% darker
                        shade_factor = 0.6

                    try:
                        # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
                        # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
                        uniforms["diffuse_color"] = [
                            shade_factor * int(material_color[1:3], 16) / 255,
                            shade_factor * int(material_color[3:5], 16) / 255,
                            shade_factor * int(material_color[5:7], 16) / 255,
                            1.0
                        ]

                        # Color the currently selected face-id.
                        face = Selection.getSelectedFace()
                        uniforms["selected_face"] = (
                            Selection.getMaxFaceSelectionId() +
                            1) if not face or node != face[0] else face[1]
                    except ValueError:
                        pass

                    if node.callDecoration("isNonPrintingMesh"):
                        if per_mesh_stack and (per_mesh_stack.getProperty(
                                "infill_mesh", "value")
                                               or per_mesh_stack.getProperty(
                                                   "cutting_mesh", "value")):
                            renderer.queueNode(
                                node,
                                shader=self._non_printing_shader,
                                uniforms=uniforms,
                                transparent=True)
                        else:
                            renderer.queueNode(
                                node,
                                shader=self._non_printing_shader,
                                transparent=True)
                    elif getattr(node, "_outside_buildarea", False):
                        renderer.queueNode(node, shader=self._disabled_shader)
                    elif per_mesh_stack and per_mesh_stack.getProperty(
                            "support_mesh", "value"):
                        # Render support meshes with a vertical stripe that is darker
                        shade_factor = 0.6
                        uniforms["diffuse_color_2"] = [
                            uniforms["diffuse_color"][0] * shade_factor,
                            uniforms["diffuse_color"][1] * shade_factor,
                            uniforms["diffuse_color"][2] * shade_factor, 1.0
                        ]
                        renderer.queueNode(node,
                                           shader=self._support_mesh_shader,
                                           uniforms=uniforms)
                    else:
                        renderer.queueNode(node,
                                           shader=self._enabled_shader,
                                           uniforms=uniforms)
                if node.callDecoration("isGroup") and Selection.isSelected(
                        node):
                    renderer.queueNode(scene.getRoot(),
                                       mesh=node.getBoundingBoxMesh(),
                                       mode=RenderBatch.RenderMode.LineLoop)
    def _onNodeDecoratorsChanged(self, node):
        self._color = Color(35, 35, 35, 0.5)

        if not node:
            return
예제 #14
0
class BuildVolume(SceneNode):
    VolumeOutlineColor = Color(12, 169, 227, 255)

    def __init__(self, parent = None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self.setCalculateBoundingBox(False)
        self._volume_aabb = None

        self._active_container_stack = None
        Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
        self._onGlobalContainerStackChanged()

    def setWidth(self, width):
        if width: self._width = width

    def setHeight(self, height):
        if height: self._height = height

    def setDepth(self, depth):
        if depth: self._depth = depth

    def getDisallowedAreas(self):
        return self._disallowed_areas

    def setDisallowedAreas(self, areas):
        self._disallowed_areas = areas

    def render(self, renderer):
        if not self.getMeshData():
            return True

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
            self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.shader"))

        renderer.queueNode(self, mode = RenderBatch.RenderMode.Lines)
        renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True)
        if self._disallowed_area_mesh:
            renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
        return True

    ##  Recalculates the build volume & disallowed areas.
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)

        self.setMeshData(mb.build())

        mb = MeshBuilder()
        mb.addQuad(
            Vector(min_w, min_h - 0.2, min_d),
            Vector(max_w, min_h - 0.2, min_d),
            Vector(max_w, min_h - 0.2, max_d),
            Vector(min_w, min_h - 0.2, max_d)
        )
        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color = color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        self._volume_aabb = AxisAlignedBox(minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h, max_d))

        skirt_size = 0.0

        container_stack = Application.getInstance().getGlobalContainerStack()
        if container_stack:
            skirt_size = self._getSkirtSize(container_stack)

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum = Vector(min_w + skirt_size + 1, min_h, min_d + disallowed_area_size - skirt_size + 1),
            maximum = Vector(max_w - skirt_size - 1, max_h, max_d - disallowed_area_size + skirt_size - 1)
        )

        Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds

    def getBoundingBox(self):
        return self._volume_aabb

    def _onGlobalContainerStackChanged(self):
        if self._active_container_stack:
            self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)

        self._active_container_stack = Application.getInstance().getGlobalContainerStack()

        if self._active_container_stack:
            self._active_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)

            self._width = self._active_container_stack.getProperty("machine_width", "value")
            if self._active_container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
                self._height = self._active_container_stack.getProperty("gantry_height", "value")
            else:
                self._height = self._active_container_stack.getProperty("machine_height", "value")
            self._depth = self._active_container_stack.getProperty("machine_depth", "value")

            self._updateDisallowedAreas()

            self.rebuild()

    def _onSettingPropertyChanged(self, setting_key, property_name):
        if property_name != "value":
            return

        if setting_key == "print_sequence":
            if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time":
                self._height = self._active_container_stack.getProperty("gantry_height", "value")
            else:
                self._height = self._active_container_stack.getProperty("machine_height", "value")
            self.rebuild()
        if setting_key in self._skirt_settings:
            self._updateDisallowedAreas()
            self.rebuild()

    def _updateDisallowedAreas(self):
        if not self._active_container_stack:
            return

        disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value")
        areas = []

        skirt_size = self._getSkirtSize(self._active_container_stack)

        if disallowed_areas:
            # Extend every area already in the disallowed_areas with the skirt size.
            for area in disallowed_areas:
                poly = Polygon(numpy.array(area, numpy.float32))
                poly = poly.getMinkowskiHull(Polygon(numpy.array([
                    [-skirt_size, 0],
                    [-skirt_size * 0.707, skirt_size * 0.707],
                    [0, skirt_size],
                    [skirt_size * 0.707, skirt_size * 0.707],
                    [skirt_size, 0],
                    [skirt_size * 0.707, -skirt_size * 0.707],
                    [0, -skirt_size],
                    [-skirt_size * 0.707, -skirt_size * 0.707]
                ], numpy.float32)))

                areas.append(poly)

        # Add the skirt areas around the borders of the build plate.
        if skirt_size > 0:
            half_machine_width = self._active_container_stack.getProperty("machine_width", "value") / 2
            half_machine_depth = self._active_container_stack.getProperty("machine_depth", "value") / 2

            areas.append(Polygon(numpy.array([
                [-half_machine_width, -half_machine_depth],
                [-half_machine_width, half_machine_depth],
                [-half_machine_width + skirt_size, half_machine_depth - skirt_size],
                [-half_machine_width + skirt_size, -half_machine_depth + skirt_size]
            ], numpy.float32)))

            areas.append(Polygon(numpy.array([
                [half_machine_width, half_machine_depth],
                [half_machine_width, -half_machine_depth],
                [half_machine_width - skirt_size, -half_machine_depth + skirt_size],
                [half_machine_width - skirt_size, half_machine_depth - skirt_size]
            ], numpy.float32)))

            areas.append(Polygon(numpy.array([
                [-half_machine_width, half_machine_depth],
                [half_machine_width, half_machine_depth],
                [half_machine_width - skirt_size, half_machine_depth - skirt_size],
                [-half_machine_width + skirt_size, half_machine_depth - skirt_size]
            ], numpy.float32)))

            areas.append(Polygon(numpy.array([
                [half_machine_width, -half_machine_depth],
                [-half_machine_width, -half_machine_depth],
                [-half_machine_width + skirt_size, -half_machine_depth + skirt_size],
                [half_machine_width - skirt_size, -half_machine_depth + skirt_size]
            ], numpy.float32)))

        self._disallowed_areas = areas

    ##  Convenience function to calculate the size of the bed adhesion.
    def _getSkirtSize(self, container_stack):
        skirt_size = 0.0

        adhesion_type = container_stack.getProperty("adhesion_type", "value")
        if adhesion_type == "skirt":
            skirt_distance = container_stack.getProperty("skirt_gap", "value")
            skirt_line_count = container_stack.getProperty("skirt_line_count", "value")
            skirt_size = skirt_distance + (skirt_line_count * container_stack.getProperty("skirt_line_width", "value"))
        elif adhesion_type == "brim":
            skirt_size = container_stack.getProperty("brim_line_count", "value") * container_stack.getProperty("skirt_line_width", "value")
        elif adhesion_type == "raft":
            skirt_size = container_stack.getProperty("raft_margin", "value")

        if container_stack.getProperty("draft_shield_enabled", "value"):
            skirt_size += container_stack.getProperty("draft_shield_dist", "value")

        if container_stack.getProperty("xy_offset", "value"):
            skirt_size += container_stack.getProperty("xy_offset", "value")

        return skirt_size

    def _clamp(self, value, min_value, max_value):
        return max(min(value, max_value), min_value)

    _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
예제 #15
0
    def event(self, event):
        modifiers = QApplication.keyboardModifiers()
        ctrl_is_active = modifiers & Qt.ControlModifier
        shift_is_active = modifiers & Qt.ShiftModifier
        if event.type == Event.KeyPressEvent and ctrl_is_active:
            amount = 10 if shift_is_active else 1
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + amount)
                return True
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - amount)
                return True

        if event.type == Event.ViewActivateEvent:
            # Make sure the SimulationPass is created
            layer_pass = self.getSimulationPass()
            self.getRenderer().addRenderPass(layer_pass)

            # Make sure the NozzleNode is add to the root
            nozzle = self.getNozzleNode()
            nozzle.setParent(self.getController().getScene().getRoot())
            nozzle.setVisible(False)

            Application.getInstance().globalContainerStackChanged.connect(
                self._onGlobalStackChanged)
            self._onGlobalStackChanged()

            if not self._simulationview_composite_shader:
                self._simulationview_composite_shader = OpenGL.getInstance(
                ).createShaderProgram(
                    os.path.join(
                        PluginRegistry.getInstance().getPluginPath(
                            "SimulationView"),
                        "simulationview_composite.shader"))
                theme = Application.getInstance().getTheme()
                self._simulationview_composite_shader.setUniformValue(
                    "u_background_color",
                    Color(*theme.getColor("viewport_background").getRgb()))
                self._simulationview_composite_shader.setUniformValue(
                    "u_outline_color",
                    Color(*theme.getColor("model_selection_outline").getRgb()))

            if not self._composite_pass:
                self._composite_pass = self.getRenderer().getRenderPass(
                    "composite")

            self._old_layer_bindings = self._composite_pass.getLayerBindings(
            )[:]  # make a copy so we can restore to it later
            self._composite_pass.getLayerBindings().append("simulationview")
            self._old_composite_shader = self._composite_pass.getCompositeShader(
            )
            self._composite_pass.setCompositeShader(
                self._simulationview_composite_shader)

        elif event.type == Event.ViewDeactivateEvent:
            self._wireprint_warning_message.hide()
            Application.getInstance().globalContainerStackChanged.disconnect(
                self._onGlobalStackChanged)
            if self._global_container_stack:
                self._global_container_stack.propertyChanged.disconnect(
                    self._onPropertyChanged)

            self._nozzle_node.setParent(None)
            self.getRenderer().removeRenderPass(self._layer_pass)
            self._composite_pass.setLayerBindings(self._old_layer_bindings)
            self._composite_pass.setCompositeShader(self._old_composite_shader)
예제 #16
0
class BuildVolume(SceneNode):
    VolumeOutlineColor = Color(12, 169, 227, 255)
    XAxisColor = Color(255, 0, 0, 255)
    YAxisColor = Color(0, 0, 255, 255)
    ZAxisColor = Color(0, 255, 0, 255)

    raftThicknessChanged = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._origin_mesh = None
        self._origin_line_length = 20
        self._origin_line_width = 0.5

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self._error_areas = []
        self._error_mesh = None

        self.setCalculateBoundingBox(False)
        self._volume_aabb = None

        self._raft_thickness = 0.0
        self._adhesion_type = None
        self._platform = Platform(self)

        self._global_container_stack = None
        Application.getInstance().globalContainerStackChanged.connect(
            self._onStackChanged)
        self._onStackChanged()

        self._has_errors = False
        Application.getInstance().getController().getScene(
        ).sceneChanged.connect(self._onSceneChanged)

        #Objects loaded at the moment. We are connected to the property changed events of these objects.
        self._scene_objects = set()

        self._change_timer = QTimer()
        self._change_timer.setInterval(100)
        self._change_timer.setSingleShot(True)
        self._change_timer.timeout.connect(self._onChangeTimerFinished)

        self._build_volume_message = Message(
            catalog.i18nc(
                "@info:status",
                "The build volume height has been reduced due to the value of the"
                " \"Print Sequence\" setting to prevent the gantry from colliding"
                " with printed models."))

        # Must be after setting _build_volume_message, apparently that is used in getMachineManager.
        # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
        # Therefore this works.
        Application.getInstance().getMachineManager(
        ).activeQualityChanged.connect(self._onStackChanged)
        # This should also ways work, and it is semantically more correct,
        # but it does not update the disallowed areas after material change
        Application.getInstance().getMachineManager(
        ).activeStackChanged.connect(self._onStackChanged)

    def _onSceneChanged(self, source):
        if self._global_container_stack:
            self._change_timer.start()

    def _onChangeTimerFinished(self):
        root = Application.getInstance().getController().getScene().getRoot()
        new_scene_objects = set(
            node for node in BreadthFirstIterator(root)
            if node.getMeshData() and type(node) is SceneNode)
        if new_scene_objects != self._scene_objects:
            for node in new_scene_objects - self._scene_objects:  #Nodes that were added to the scene.
                node.decoratorsChanged.connect(self._onNodeDecoratorChanged)
            for node in self._scene_objects - new_scene_objects:  #Nodes that were removed from the scene.
                per_mesh_stack = node.callDecoration("getStack")
                if per_mesh_stack:
                    per_mesh_stack.propertyChanged.disconnect(
                        self._onSettingPropertyChanged)
                active_extruder_changed = node.callDecoration(
                    "getActiveExtruderChangedSignal")
                if active_extruder_changed is not None:
                    node.callDecoration(
                        "getActiveExtruderChangedSignal").disconnect(
                            self._updateDisallowedAreasAndRebuild)
                node.decoratorsChanged.disconnect(self._onNodeDecoratorChanged)

            self._scene_objects = new_scene_objects
            self._onSettingPropertyChanged(
                "print_sequence",
                "value")  # Create fake event, so right settings are triggered.

    ##  Updates the listeners that listen for changes in per-mesh stacks.
    #
    #   \param node The node for which the decorators changed.
    def _onNodeDecoratorChanged(self, node):
        per_mesh_stack = node.callDecoration("getStack")
        if per_mesh_stack:
            per_mesh_stack.propertyChanged.connect(
                self._onSettingPropertyChanged)
        active_extruder_changed = node.callDecoration(
            "getActiveExtruderChangedSignal")
        if active_extruder_changed is not None:
            active_extruder_changed.connect(
                self._updateDisallowedAreasAndRebuild)
            self._updateDisallowedAreasAndRebuild()

    def setWidth(self, width):
        if width: self._width = width

    def setHeight(self, height):
        if height: self._height = height

    def setDepth(self, depth):
        if depth: self._depth = depth

    def getDisallowedAreas(self):
        return self._disallowed_areas

    def setDisallowedAreas(self, areas):
        self._disallowed_areas = areas

    def render(self, renderer):
        if not self.getMeshData():
            return True

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "default.shader"))
            self._grid_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "grid.shader"))

        renderer.queueNode(self, mode=RenderBatch.RenderMode.Lines)
        renderer.queueNode(self, mesh=self._origin_mesh)
        renderer.queueNode(self,
                           mesh=self._grid_mesh,
                           shader=self._grid_shader,
                           backface_cull=True)
        if self._disallowed_area_mesh:
            renderer.queueNode(self,
                               mesh=self._disallowed_area_mesh,
                               shader=self._shader,
                               transparent=True,
                               backface_cull=True,
                               sort=-9)

        if self._error_mesh:
            renderer.queueNode(self,
                               mesh=self._error_mesh,
                               shader=self._shader,
                               transparent=True,
                               backface_cull=True,
                               sort=-8)

        return True

    ##  Recalculates the build volume & disallowed areas.
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        # Outline 'cube' of the build volume
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(max_w, min_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        self.setMeshData(mb.build())

        mb = MeshBuilder()

        # Indication of the machine origin
        if self._global_container_stack.getProperty("machine_center_is_zero",
                                                    "value"):
            origin = (Vector(min_w, min_h, min_d) +
                      Vector(max_w, min_h, max_d)) / 2
        else:
            origin = Vector(min_w, min_h, max_d)

        mb.addCube(width=self._origin_line_length,
                   height=self._origin_line_width,
                   depth=self._origin_line_width,
                   center=origin + Vector(self._origin_line_length / 2, 0, 0),
                   color=self.XAxisColor)
        mb.addCube(width=self._origin_line_width,
                   height=self._origin_line_length,
                   depth=self._origin_line_width,
                   center=origin + Vector(0, self._origin_line_length / 2, 0),
                   color=self.YAxisColor)
        mb.addCube(width=self._origin_line_width,
                   height=self._origin_line_width,
                   depth=self._origin_line_length,
                   center=origin - Vector(0, 0, self._origin_line_length / 2),
                   color=self.ZAxisColor)
        self._origin_mesh = mb.build()

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))

        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(
                        points[:, 1]
                ) >= 0:  # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(
                        numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        if self._error_areas:
            mb = MeshBuilder()
            for error_area in self._error_areas:
                color = Color(1.0, 0.0, 0.0, 0.5)
                points = error_area.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point
            self._error_mesh = mb.build()
        else:
            self._error_mesh = None

        self._volume_aabb = AxisAlignedBox(
            minimum=Vector(min_w, min_h - 1.0, min_d),
            maximum=Vector(max_w, max_h - self._raft_thickness, max_d))

        bed_adhesion_size = self._getEdgeDisallowedSize()

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(
                min_w + bed_adhesion_size + 1, min_h,
                min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum=Vector(
                max_w - bed_adhesion_size - 1, max_h - self._raft_thickness,
                max_d - disallowed_area_size + bed_adhesion_size - 1))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds

    def getBoundingBox(self):
        return self._volume_aabb

    def getRaftThickness(self):
        return self._raft_thickness

    def _updateRaftThickness(self):
        old_raft_thickness = self._raft_thickness
        self._adhesion_type = self._global_container_stack.getProperty(
            "adhesion_type", "value")
        self._raft_thickness = 0.0
        if self._adhesion_type == "raft":
            self._raft_thickness = (self._global_container_stack.getProperty(
                "raft_base_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_interface_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_surface_layers", "value") *
                                    self._global_container_stack.getProperty(
                                        "raft_surface_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_airgap", "value"))

        # Rounding errors do not matter, we check if raft_thickness has changed at all
        if old_raft_thickness != self._raft_thickness:
            self.setPosition(Vector(0, -self._raft_thickness, 0),
                             SceneNode.TransformSpace.World)
            self.raftThicknessChanged.emit()

    ##  Update the build volume visualization
    def _onStackChanged(self):
        if self._global_container_stack:
            self._global_container_stack.propertyChanged.disconnect(
                self._onSettingPropertyChanged)
            extruders = ExtruderManager.getInstance().getMachineExtruders(
                self._global_container_stack.getId())
            for extruder in extruders:
                extruder.propertyChanged.disconnect(
                    self._onSettingPropertyChanged)

        self._global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()

        if self._global_container_stack:
            self._global_container_stack.propertyChanged.connect(
                self._onSettingPropertyChanged)
            extruders = ExtruderManager.getInstance().getMachineExtruders(
                self._global_container_stack.getId())
            for extruder in extruders:
                extruder.propertyChanged.connect(
                    self._onSettingPropertyChanged)

            self._width = self._global_container_stack.getProperty(
                "machine_width", "value")
            machine_height = self._global_container_stack.getProperty(
                "machine_height", "value")
            if self._global_container_stack.getProperty(
                    "print_sequence", "value") == "one_at_a_time" and len(
                        self._scene_objects) > 1:
                self._height = min(
                    self._global_container_stack.getProperty(
                        "gantry_height", "value"), machine_height)
                if self._height < machine_height:
                    self._build_volume_message.show()
                else:
                    self._build_volume_message.hide()
            else:
                self._height = self._global_container_stack.getProperty(
                    "machine_height", "value")
                self._build_volume_message.hide()
            self._depth = self._global_container_stack.getProperty(
                "machine_depth", "value")

            self._updateDisallowedAreas()
            self._updateRaftThickness()

            self.rebuild()

    def _onSettingPropertyChanged(self, setting_key, property_name):
        if property_name != "value":
            return

        rebuild_me = False
        if setting_key == "print_sequence":
            machine_height = self._global_container_stack.getProperty(
                "machine_height", "value")
            if Application.getInstance().getGlobalContainerStack().getProperty(
                    "print_sequence", "value") == "one_at_a_time" and len(
                        self._scene_objects) > 1:
                self._height = min(
                    self._global_container_stack.getProperty(
                        "gantry_height", "value"), machine_height)
                if self._height < machine_height:
                    self._build_volume_message.show()
                else:
                    self._build_volume_message.hide()
            else:
                self._height = self._global_container_stack.getProperty(
                    "machine_height", "value")
                self._build_volume_message.hide()
            rebuild_me = True

        if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence" or setting_key in self._ooze_shield_settings or setting_key in self._distance_settings or setting_key in self._extruder_settings:
            self._updateDisallowedAreas()
            rebuild_me = True

        if setting_key in self._raft_settings:
            self._updateRaftThickness()
            rebuild_me = True

        if rebuild_me:
            self.rebuild()

    def hasErrors(self):
        return self._has_errors

    ##  Calls _updateDisallowedAreas and makes sure the changes appear in the
    #   scene.
    #
    #   This is required for a signal to trigger the update in one go. The
    #   ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``,
    #   since there may be other changes before it needs to be rebuilt, which
    #   would hit performance.
    def _updateDisallowedAreasAndRebuild(self):
        self._updateDisallowedAreas()
        self.rebuild()

    def _updateDisallowedAreas(self):
        if not self._global_container_stack:
            return

        self._error_areas = []

        extruder_manager = ExtruderManager.getInstance()
        used_extruders = extruder_manager.getUsedExtruderStacks()
        disallowed_border_size = self._getEdgeDisallowedSize()

        result_areas = self._computeDisallowedAreasStatic(
            disallowed_border_size, used_extruders
        )  #Normal machine disallowed areas can always be added.
        prime_areas = self._computeDisallowedAreasPrime(
            disallowed_border_size, used_extruders)
        prime_disallowed_areas = self._computeDisallowedAreasStatic(
            0, used_extruders
        )  #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.

        #Check if prime positions intersect with disallowed areas.
        for extruder in used_extruders:
            extruder_id = extruder.getId()

            collision = False
            for prime_polygon in prime_areas[extruder_id]:
                for disallowed_polygon in prime_disallowed_areas[extruder_id]:
                    if prime_polygon.intersectsPolygon(
                            disallowed_polygon) is not None:
                        collision = True
                        break
                if collision:
                    break

                #Also check other prime positions (without additional offset).
                for other_extruder_id in prime_areas:
                    if extruder_id == other_extruder_id:  #It is allowed to collide with itself.
                        continue
                    for other_prime_polygon in prime_areas[other_extruder_id]:
                        if prime_polygon.intersectsPolygon(
                                other_prime_polygon):
                            collision = True
                            break
                    if collision:
                        break
                if collision:
                    break

            if not collision:
                #Prime areas are valid. Add as normal.
                result_areas[extruder_id].extend(prime_areas[extruder_id])

            nozzle_disallowed_areas = extruder.getProperty(
                "nozzle_disallowed_areas", "value")
            for area in nozzle_disallowed_areas:
                polygon = Polygon(numpy.array(area, numpy.float32))
                polygon = polygon.getMinkowskiHull(
                    Polygon.approximatedCircle(disallowed_border_size))
                result_areas[extruder_id].append(
                    polygon)  #Don't perform the offset on these.

        # Add prime tower location as disallowed area.
        prime_tower_collision = False
        prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
        for extruder_id in prime_tower_areas:
            for prime_tower_area in prime_tower_areas[extruder_id]:
                for area in result_areas[extruder_id]:
                    if prime_tower_area.intersectsPolygon(area) is not None:
                        prime_tower_collision = True
                        break
                if prime_tower_collision:  #Already found a collision.
                    break
            if not prime_tower_collision:
                result_areas[extruder_id].extend(
                    prime_tower_areas[extruder_id])
            else:
                self._error_areas.extend(prime_tower_areas[extruder_id])

        self._has_errors = len(self._error_areas) > 0

        self._disallowed_areas = []
        for extruder_id in result_areas:
            self._disallowed_areas.extend(result_areas[extruder_id])

    ##  Computes the disallowed areas for objects that are printed with print
    #   features.
    #
    #   This means that the brim, travel avoidance and such will be applied to
    #   these features.
    #
    #   \return A dictionary with for each used extruder ID the disallowed areas
    #   where that extruder may not print.
    def _computeDisallowedAreasPrinted(self, used_extruders):
        result = {}
        for extruder in used_extruders:
            result[extruder.getId()] = []

        #Currently, the only normally printed object is the prime tower.
        if ExtruderManager.getInstance().getResolveOrValue(
                "prime_tower_enable") == True:
            prime_tower_size = self._global_container_stack.getProperty(
                "prime_tower_size", "value")
            machine_width = self._global_container_stack.getProperty(
                "machine_width", "value")
            machine_depth = self._global_container_stack.getProperty(
                "machine_depth", "value")
            prime_tower_x = self._global_container_stack.getProperty(
                "prime_tower_position_x", "value"
            ) - machine_width / 2  #Offset by half machine_width and _depth to put the origin in the front-left.
            prime_tower_y = -self._global_container_stack.getProperty(
                "prime_tower_position_y", "value") + machine_depth / 2

            prime_tower_area = Polygon([
                [
                    prime_tower_x - prime_tower_size,
                    prime_tower_y - prime_tower_size
                ],
                [prime_tower_x, prime_tower_y - prime_tower_size],
                [prime_tower_x, prime_tower_y],
                [prime_tower_x - prime_tower_size, prime_tower_y],
            ])
            prime_tower_area = prime_tower_area.getMinkowskiHull(
                Polygon.approximatedCircle(0))
            for extruder in used_extruders:
                result[extruder.getId()].append(
                    prime_tower_area
                )  #The prime tower location is the same for each extruder, regardless of offset.

        return result

    ##  Computes the disallowed areas for the prime locations.
    #
    #   These are special because they are not subject to things like brim or
    #   travel avoidance. They do get a dilute with the border size though
    #   because they may not intersect with brims and such of other objects.
    #
    #   \param border_size The size with which to offset the disallowed areas
    #   due to skirt, brim, travel avoid distance, etc.
    #   \param used_extruders The extruder stacks to generate disallowed areas
    #   for.
    #   \return A dictionary with for each used extruder ID the prime areas.
    def _computeDisallowedAreasPrime(self, border_size, used_extruders):
        result = {}

        machine_width = self._global_container_stack.getProperty(
            "machine_width", "value")
        machine_depth = self._global_container_stack.getProperty(
            "machine_depth", "value")
        for extruder in used_extruders:
            prime_x = extruder.getProperty(
                "extruder_prime_pos_x", "value"
            ) - machine_width / 2  #Offset by half machine_width and _depth to put the origin in the front-left.
            prime_y = machine_depth / 2 - extruder.getProperty(
                "extruder_prime_pos_y", "value")

            prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE)
            prime_polygon = prime_polygon.translate(prime_x, prime_y)
            prime_polygon = prime_polygon.getMinkowskiHull(
                Polygon.approximatedCircle(border_size))
            result[extruder.getId()] = [prime_polygon]

        return result

    ##  Computes the disallowed areas that are statically placed in the machine.
    #
    #   It computes different disallowed areas depending on the offset of the
    #   extruder. The resulting dictionary will therefore have an entry for each
    #   extruder that is used.
    #
    #   \param border_size The size with which to offset the disallowed areas
    #   due to skirt, brim, travel avoid distance, etc.
    #   \param used_extruders The extruder stacks to generate disallowed areas
    #   for.
    #   \return A dictionary with for each used extruder ID the disallowed areas
    #   where that extruder may not print.
    def _computeDisallowedAreasStatic(self, border_size, used_extruders):
        #Convert disallowed areas to polygons and dilate them.
        machine_disallowed_polygons = []
        for area in self._global_container_stack.getProperty(
                "machine_disallowed_areas", "value"):
            polygon = Polygon(numpy.array(area, numpy.float32))
            polygon = polygon.getMinkowskiHull(
                Polygon.approximatedCircle(border_size))
            machine_disallowed_polygons.append(polygon)

        result = {}
        for extruder in used_extruders:
            extruder_id = extruder.getId()
            offset_x = extruder.getProperty("machine_nozzle_offset_x", "value")
            if offset_x is None:
                offset_x = 0
            offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
            if offset_y is None:
                offset_y = 0
            result[extruder_id] = []

            for polygon in machine_disallowed_polygons:
                result[extruder_id].append(
                    polygon.translate(offset_x, offset_y)
                )  #Compensate for the nozzle offset of this extruder.

            #Add the border around the edge of the build volume.
            left_unreachable_border = 0
            right_unreachable_border = 0
            top_unreachable_border = 0
            bottom_unreachable_border = 0
            #The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
            for other_extruder in ExtruderManager.getInstance(
            ).getActiveExtruderStacks():
                other_offset_x = other_extruder.getProperty(
                    "machine_nozzle_offset_x", "value")
                other_offset_y = other_extruder.getProperty(
                    "machine_nozzle_offset_y", "value")
                left_unreachable_border = min(left_unreachable_border,
                                              other_offset_x - offset_x)
                right_unreachable_border = max(right_unreachable_border,
                                               other_offset_x - offset_x)
                top_unreachable_border = min(top_unreachable_border,
                                             other_offset_y - offset_y)
                bottom_unreachable_border = max(bottom_unreachable_border,
                                                other_offset_y - offset_y)
            half_machine_width = self._global_container_stack.getProperty(
                "machine_width", "value") / 2
            half_machine_depth = self._global_container_stack.getProperty(
                "machine_depth", "value") / 2
            if border_size - left_unreachable_border > 0:
                result[extruder_id].append(
                    Polygon(
                        numpy.array(
                            [[-half_machine_width, -half_machine_depth],
                             [-half_machine_width, half_machine_depth],
                             [
                                 -half_machine_width + border_size -
                                 left_unreachable_border, half_machine_depth -
                                 border_size - bottom_unreachable_border
                             ],
                             [
                                 -half_machine_width + border_size -
                                 left_unreachable_border, -half_machine_depth +
                                 border_size - top_unreachable_border
                             ]], numpy.float32)))
            if border_size + right_unreachable_border > 0:
                result[extruder_id].append(
                    Polygon(
                        numpy.array(
                            [[half_machine_width, half_machine_depth],
                             [half_machine_width, -half_machine_depth],
                             [
                                 half_machine_width - border_size -
                                 right_unreachable_border,
                                 -half_machine_depth + border_size -
                                 top_unreachable_border
                             ],
                             [
                                 half_machine_width - border_size -
                                 right_unreachable_border, half_machine_depth -
                                 border_size - bottom_unreachable_border
                             ]], numpy.float32)))
            if border_size + bottom_unreachable_border > 0:
                result[extruder_id].append(
                    Polygon(
                        numpy.array(
                            [[-half_machine_width, half_machine_depth],
                             [half_machine_width, half_machine_depth],
                             [
                                 half_machine_width - border_size -
                                 right_unreachable_border, half_machine_depth -
                                 border_size - bottom_unreachable_border
                             ],
                             [
                                 -half_machine_width + border_size -
                                 left_unreachable_border, half_machine_depth -
                                 border_size - bottom_unreachable_border
                             ]], numpy.float32)))
            if border_size - top_unreachable_border > 0:
                result[extruder_id].append(
                    Polygon(
                        numpy.array(
                            [[half_machine_width, -half_machine_depth],
                             [-half_machine_width, -half_machine_depth],
                             [
                                 -half_machine_width + border_size -
                                 left_unreachable_border, -half_machine_depth +
                                 border_size - top_unreachable_border
                             ],
                             [
                                 half_machine_width - border_size -
                                 right_unreachable_border,
                                 -half_machine_depth + border_size -
                                 top_unreachable_border
                             ]], numpy.float32)))

        return result

    ##  Private convenience function to get a setting from the adhesion
    #   extruder.
    #
    #   \param setting_key The key of the setting to get.
    #   \param property The property to get from the setting.
    #   \return The property of the specified setting in the adhesion extruder.
    def _getSettingFromAdhesionExtruder(self, setting_key, property="value"):
        return self._getSettingFromExtruder(setting_key,
                                            "adhesion_extruder_nr", property)

    ##  Private convenience function to get a setting from every extruder.
    #
    #   For single extrusion machines, this gets the setting from the global
    #   stack.
    #
    #   \return A sequence of setting values, one for each extruder.
    def _getSettingFromAllExtruders(self, setting_key, property="value"):
        return ExtruderManager.getInstance().getAllExtruderSettings(
            setting_key, property)

    ##  Private convenience function to get a setting from the support infill
    #   extruder.
    #
    #   \param setting_key The key of the setting to get.
    #   \param property The property to get from the setting.
    #   \return The property of the specified setting in the support infill
    #   extruder.
    def _getSettingFromSupportInfillExtruder(self,
                                             setting_key,
                                             property="value"):
        return self._getSettingFromExtruder(setting_key,
                                            "support_infill_extruder_nr",
                                            property)

    ##  Helper function to get a setting from an extruder specified in another
    #   setting.
    #
    #   \param setting_key The key of the setting to get.
    #   \param extruder_setting_key The key of the setting that specifies from
    #   which extruder to get the setting, if there are multiple extruders.
    #   \param property The property to get from the setting.
    #   \return The property of the specified setting in the specified extruder.
    def _getSettingFromExtruder(self,
                                setting_key,
                                extruder_setting_key,
                                property="value"):
        multi_extrusion = self._global_container_stack.getProperty(
            "machine_extruder_count", "value") > 1

        if not multi_extrusion:
            return self._global_container_stack.getProperty(
                setting_key, property)

        extruder_index = self._global_container_stack.getProperty(
            extruder_setting_key, "value")

        if extruder_index == "-1":  # If extruder index is -1 use global instead
            return self._global_container_stack.getProperty(
                setting_key, property)

        extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(
            extruder_index)]
        stack = UM.Settings.ContainerRegistry.getInstance(
        ).findContainerStacks(id=extruder_stack_id)[0]
        return stack.getProperty(setting_key, property)

    ##  Convenience function to calculate the disallowed radius around the edge.
    #
    #   This disallowed radius is to allow for space around the models that is
    #   not part of the collision radius, such as bed adhesion (skirt/brim/raft)
    #   and travel avoid distance.
    def _getEdgeDisallowedSize(self):
        if not self._global_container_stack:
            return 0
        container_stack = self._global_container_stack

        # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
        if container_stack.getProperty("print_sequence",
                                       "value") == "one_at_a_time":
            return 0.1  # Return a very small value, so we do draw disallowed area's near the edges.

        adhesion_type = container_stack.getProperty("adhesion_type", "value")
        if adhesion_type == "skirt":
            skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
            skirt_line_count = self._getSettingFromAdhesionExtruder(
                "skirt_line_count")
            bed_adhesion_size = skirt_distance + (
                skirt_line_count *
                self._getSettingFromAdhesionExtruder("skirt_brim_line_width"))
            if self._global_container_stack.getProperty(
                    "machine_extruder_count", "value") > 1:
                adhesion_extruder_nr = int(
                    self._global_container_stack.getProperty(
                        "adhesion_extruder_nr", "value"))
                extruder_values = ExtruderManager.getInstance(
                ).getAllExtruderValues("skirt_brim_line_width")
                del extruder_values[
                    adhesion_extruder_nr]  # Remove the value of the adhesion extruder nr.
                for value in extruder_values:
                    bed_adhesion_size += value
        elif adhesion_type == "brim":
            bed_adhesion_size = self._getSettingFromAdhesionExtruder(
                "brim_line_count") * self._getSettingFromAdhesionExtruder(
                    "skirt_brim_line_width")
            if self._global_container_stack.getProperty(
                    "machine_extruder_count", "value") > 1:
                adhesion_extruder_nr = int(
                    self._global_container_stack.getProperty(
                        "adhesion_extruder_nr", "value"))
                extruder_values = ExtruderManager.getInstance(
                ).getAllExtruderValues("skirt_brim_line_width")
                del extruder_values[
                    adhesion_extruder_nr]  # Remove the value of the adhesion extruder nr.
                for value in extruder_values:
                    bed_adhesion_size += value
        elif adhesion_type == "raft":
            bed_adhesion_size = self._getSettingFromAdhesionExtruder(
                "raft_margin")
        elif adhesion_type == "none":
            bed_adhesion_size = 0
        else:
            raise Exception(
                "Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?"
            )

        support_expansion = 0
        if self._getSettingFromSupportInfillExtruder(
                "support_offset") and self._global_container_stack.getProperty(
                    "support_enable", "value"):
            support_expansion += self._getSettingFromSupportInfillExtruder(
                "support_offset")

        farthest_shield_distance = 0
        if container_stack.getProperty("draft_shield_enabled", "value"):
            farthest_shield_distance = max(
                farthest_shield_distance,
                container_stack.getProperty("draft_shield_dist", "value"))
        if container_stack.getProperty("ooze_shield_enabled", "value"):
            farthest_shield_distance = max(
                farthest_shield_distance,
                container_stack.getProperty("ooze_shield_dist", "value"))

        move_from_wall_radius = 0  # Moves that start from outer wall.
        move_from_wall_radius = max(
            move_from_wall_radius,
            max(self._getSettingFromAllExtruders("infill_wipe_dist")))
        avoid_enabled_per_extruder = self._getSettingFromAllExtruders(
            ("travel_avoid_other_parts"))
        avoid_distance_per_extruder = self._getSettingFromAllExtruders(
            "travel_avoid_distance")
        for index, avoid_other_parts_enabled in enumerate(
                avoid_enabled_per_extruder
        ):  #For each extruder (or just global).
            if avoid_other_parts_enabled:
                move_from_wall_radius = max(move_from_wall_radius,
                                            avoid_distance_per_extruder[index]
                                            )  #Index of the same extruder.

        #Now combine our different pieces of data to get the final border size.
        #Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
        #Support expansion is added to farthest shield distance, since the shields go around support.
        border_size = max(move_from_wall_radius,
                          support_expansion + farthest_shield_distance,
                          support_expansion + bed_adhesion_size)
        return border_size

    def _clamp(self, value, min_value, max_value):
        return max(min(value, max_value), min_value)

    _skirt_settings = [
        "adhesion_type", "skirt_gap", "skirt_line_count",
        "skirt_brim_line_width", "brim_width", "brim_line_count",
        "raft_margin", "draft_shield_enabled", "draft_shield_dist"
    ]
    _raft_settings = [
        "adhesion_type", "raft_base_thickness", "raft_interface_thickness",
        "raft_surface_layers", "raft_surface_thickness", "raft_airgap"
    ]
    _prime_settings = [
        "extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"
    ]
    _tower_settings = [
        "prime_tower_enable", "prime_tower_size", "prime_tower_position_x",
        "prime_tower_position_y"
    ]
    _ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
    _distance_settings = [
        "infill_wipe_dist", "travel_avoid_distance", "support_offset",
        "support_enable", "travel_avoid_other_parts"
    ]
    _extruder_settings = [
        "support_enable", "support_interface_enable",
        "support_infill_extruder_nr", "support_extruder_nr_layer_0",
        "support_interface_extruder_nr", "brim_line_count",
        "adhesion_extruder_nr", "adhesion_type"
    ]  #Settings that can affect which extruders are used.
예제 #17
0
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        if not Application.getInstance()._engine:
            return

        if not self._volume_outline_color:
            theme = Application.getInstance().getTheme()
            self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
            self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
            self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
            self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
            self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
            self._error_area_color = Color(*theme.getColor("error_area").getRgb())

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting

        if self._shape != "elliptic":
            # Outline 'cube' of the build volume
            mb = MeshBuilder()
            mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self._volume_outline_color)
            mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self._volume_outline_color)

            mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self._volume_outline_color)

            mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self._volume_outline_color)
            mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self._volume_outline_color)

            self.setMeshData(mb.build())

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addQuad(
                Vector(min_w, min_h - z_fight_distance, min_d),
                Vector(max_w, min_h - z_fight_distance, min_d),
                Vector(max_w, min_h - z_fight_distance, max_d),
                Vector(min_w, min_h - z_fight_distance, max_d)
            )

            for n in range(0, 6):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2])
            self._grid_mesh = mb.build()

        else:
            # Bottom and top 'ellipse' of the build volume
            aspect = 1.0
            scale_matrix = Matrix()
            if self._width != 0:
                # Scale circular meshes by aspect ratio if width != height
                aspect = self._depth / self._width
                scale_matrix.compose(scale = Vector(1, 1, aspect))
            mb = MeshBuilder()
            mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self._volume_outline_color)
            mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0),  color = self._volume_outline_color)
            self.setMeshData(mb.build().getTransformed(scale_matrix))

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addVertex(0, min_h - z_fight_distance, 0)
            mb.addArc(max_w, Vector.Unit_Y, center = Vector(0, min_h - z_fight_distance, 0))
            sections = mb.getVertexCount() - 1 # Center point is not an arc section
            indices = []
            for n in range(0, sections - 1):
                indices.append([0, n + 2, n + 1])
            mb.addIndices(numpy.asarray(indices, dtype = numpy.int32))
            mb.calculateNormals()

            for n in range(0, mb.getVertexCount()):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
            self._grid_mesh = mb.build().getTransformed(scale_matrix)

        # Indication of the machine origin
        if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
            origin = (Vector(min_w, min_h, min_d) + Vector(max_w, min_h, max_d)) / 2
        else:
            origin = Vector(min_w, min_h, max_d)

        mb = MeshBuilder()
        mb.addCube(
            width = self._origin_line_length,
            height = self._origin_line_width,
            depth = self._origin_line_width,
            center = origin + Vector(self._origin_line_length / 2, 0, 0),
            color = self._x_axis_color
        )
        mb.addCube(
            width = self._origin_line_width,
            height = self._origin_line_length,
            depth = self._origin_line_width,
            center = origin + Vector(0, self._origin_line_length / 2, 0),
            color = self._y_axis_color
        )
        mb.addCube(
            width = self._origin_line_width,
            height = self._origin_line_width,
            depth = self._origin_line_length,
            center = origin - Vector(0, 0, self._origin_line_length / 2),
            color = self._z_axis_color
        )
        self._origin_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = self._disallowed_area_color
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                if len(points) == 0:
                    continue

                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color = color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        if self._error_areas:
            mb = MeshBuilder()
            for error_area in self._error_areas:
                color = self._error_area_color
                points = error_area.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
                                        self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point
            self._error_mesh = mb.build()
        else:
            self._error_mesh = None

        self._volume_aabb = AxisAlignedBox(
            minimum = Vector(min_w, min_h - 1.0, min_d),
            maximum = Vector(max_w, max_h - self._raft_thickness - self._extra_z_clearance, max_d))

        bed_adhesion_size = self._getEdgeDisallowedSize()

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
        )

        Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
예제 #18
0
    def _checkSetup(self):
        if not self._extruders_model:
            self._extruders_model = Application.getInstance().getExtrudersModel()

        if not self._theme:
            self._theme = Application.getInstance().getTheme()

        if not self._enabled_shader:
            self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
            self._enabled_shader.setUniformValue("u_overhangColor", Color(*self._theme.getColor("model_overhang").getRgb()))

        if not self._disabled_shader:
            self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
            self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*self._theme.getColor("model_unslicable").getRgb()))
            self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*self._theme.getColor("model_unslicable_alt").getRgb()))
            self._disabled_shader.setUniformValue("u_width", 50.0)

        if not self._non_printing_shader:
            self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
            self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb()))
            self._non_printing_shader.setUniformValue("u_opacity", 0.6)

        if not self._support_mesh_shader:
            self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
            self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
            self._support_mesh_shader.setUniformValue("u_width", 5.0)

        if not Application.getInstance().getPreferences().getValue(self._show_xray_warning_preference):
            self._xray_shader = None
            self._xray_composite_shader = None
            if self._composite_pass and 'xray' in self._composite_pass.getLayerBindings():
                self._composite_pass.setLayerBindings(self._old_layer_bindings)
                self._composite_pass.setCompositeShader(self._old_composite_shader)
                self._old_layer_bindings = None
                self._old_composite_shader = None
                self._xray_warning_message.hide()
        else:
            if not self._xray_shader:
                self._xray_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray.shader"))

            if not self._xray_composite_shader:
                self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray_composite.shader"))
                theme = Application.getInstance().getTheme()
                self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
                self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))

            renderer = self.getRenderer()
            if not self._composite_pass or not 'xray' in self._composite_pass.getLayerBindings():
                # Currently the RenderPass constructor requires a size > 0
                # This should be fixed in RenderPass's constructor.
                self._xray_pass = XRayPass.XRayPass(1, 1)

                renderer.addRenderPass(self._xray_pass)

                if not self._composite_pass:
                    self._composite_pass = self.getRenderer().getRenderPass("composite")

                self._old_layer_bindings = self._composite_pass.getLayerBindings()
                self._composite_pass.setLayerBindings(["default", "selection", "xray"])
                self._old_composite_shader = self._composite_pass.getCompositeShader()
                self._composite_pass.setCompositeShader(self._xray_composite_shader)
예제 #19
0
파일: BuildVolume.py 프로젝트: won21kr/Cura
class BuildVolume(SceneNode):
    VolumeOutlineColor = Color(12, 169, 227, 255)

    raftThicknessChanged = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self.setCalculateBoundingBox(False)
        self._volume_aabb = None

        self._raft_thickness = 0.0
        self._adhesion_type = None
        self._platform = Platform(self)

        self._global_container_stack = None
        Application.getInstance().globalContainerStackChanged.connect(
            self._onGlobalContainerStackChanged)
        self._onGlobalContainerStackChanged()

        self._active_extruder_stack = None
        ExtruderManager.getInstance().activeExtruderChanged.connect(
            self._onActiveExtruderStackChanged)
        self._onActiveExtruderStackChanged()

    def setWidth(self, width):
        if width: self._width = width

    def setHeight(self, height):
        if height: self._height = height

    def setDepth(self, depth):
        if depth: self._depth = depth

    def getDisallowedAreas(self):
        return self._disallowed_areas

    def setDisallowedAreas(self, areas):
        self._disallowed_areas = areas

    def render(self, renderer):
        if not self.getMeshData():
            return True

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "default.shader"))
            self._grid_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "grid.shader"))

        renderer.queueNode(self, mode=RenderBatch.RenderMode.Lines)
        renderer.queueNode(self,
                           mesh=self._grid_mesh,
                           shader=self._grid_shader,
                           backface_cull=True)
        if self._disallowed_area_mesh:
            renderer.queueNode(self,
                               mesh=self._disallowed_area_mesh,
                               shader=self._shader,
                               transparent=True,
                               backface_cull=True,
                               sort=-9)

        return True

    ##  Recalculates the build volume & disallowed areas.
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        mb = MeshBuilder()

        # Outline 'cube' of the build volume
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(max_w, min_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, max_h, min_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, min_h, max_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, max_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        mb.addLine(Vector(min_w, min_h, min_d),
                   Vector(min_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, min_h, min_d),
                   Vector(max_w, min_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(min_w, max_h, min_d),
                   Vector(min_w, max_h, max_d),
                   color=self.VolumeOutlineColor)
        mb.addLine(Vector(max_w, max_h, min_d),
                   Vector(max_w, max_h, max_d),
                   color=self.VolumeOutlineColor)

        self.setMeshData(mb.build())

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))

        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w),
                               disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w),
                                       disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(
                        points[:, 1]
                ) >= 0:  # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(
                        numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        self._volume_aabb = AxisAlignedBox(
            minimum=Vector(min_w, min_h - 1.0, min_d),
            maximum=Vector(max_w, max_h - self._raft_thickness, max_d))

        bed_adhesion_size = 0.0

        container_stack = Application.getInstance().getGlobalContainerStack()
        if container_stack:
            bed_adhesion_size = self._getBedAdhesionSize(container_stack)

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(
                min_w + bed_adhesion_size + 1, min_h,
                min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum=Vector(
                max_w - bed_adhesion_size - 1, max_h - self._raft_thickness,
                max_d - disallowed_area_size + bed_adhesion_size - 1))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds

    def getBoundingBox(self):
        return self._volume_aabb

    def _buildVolumeMessage(self):
        Message(
            catalog.i18nc(
                "@info:status",
                "The build volume height has been reduced due to the value of the"
                " \"Print Sequence\" setting to prevent the gantry from colliding"
                " with printed models.")).show()

    def getRaftThickness(self):
        return self._raft_thickness

    def _updateRaftThickness(self):
        old_raft_thickness = self._raft_thickness
        self._adhesion_type = self._global_container_stack.getProperty(
            "adhesion_type", "value")
        self._raft_thickness = 0.0
        if self._adhesion_type == "raft":
            self._raft_thickness = (self._global_container_stack.getProperty(
                "raft_base_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_interface_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_surface_layers", "value") *
                                    self._global_container_stack.getProperty(
                                        "raft_surface_thickness", "value") +
                                    self._global_container_stack.getProperty(
                                        "raft_airgap", "value"))

        # Rounding errors do not matter, we check if raft_thickness has changed at all
        if old_raft_thickness != self._raft_thickness:
            self.setPosition(Vector(0, -self._raft_thickness, 0),
                             SceneNode.TransformSpace.World)
            self.raftThicknessChanged.emit()

    def _onGlobalContainerStackChanged(self):
        if self._global_container_stack:
            self._global_container_stack.propertyChanged.disconnect(
                self._onSettingPropertyChanged)

        self._global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()

        if self._global_container_stack:
            self._global_container_stack.propertyChanged.connect(
                self._onSettingPropertyChanged)

            self._width = self._global_container_stack.getProperty(
                "machine_width", "value")
            machine_height = self._global_container_stack.getProperty(
                "machine_height", "value")
            if self._global_container_stack.getProperty(
                    "print_sequence", "value") == "one_at_a_time":
                self._height = min(
                    self._global_container_stack.getProperty(
                        "gantry_height", "value"), machine_height)
                if self._height < machine_height:
                    self._buildVolumeMessage()
            else:
                self._height = self._global_container_stack.getProperty(
                    "machine_height", "value")
            self._depth = self._global_container_stack.getProperty(
                "machine_depth", "value")

            self._updateDisallowedAreas()
            self._updateRaftThickness()

            self.rebuild()

    def _onActiveExtruderStackChanged(self):
        if self._active_extruder_stack:
            self._active_extruder_stack.propertyChanged.disconnect(
                self._onSettingPropertyChanged)
        self._active_extruder_stack = ExtruderManager.getInstance(
        ).getActiveExtruderStack()
        if self._active_extruder_stack:
            self._active_extruder_stack.propertyChanged.connect(
                self._onSettingPropertyChanged)

    def _onSettingPropertyChanged(self, setting_key, property_name):
        if property_name != "value":
            return

        rebuild_me = False
        if setting_key == "print_sequence":
            machine_height = self._global_container_stack.getProperty(
                "machine_height", "value")
            if Application.getInstance().getGlobalContainerStack().getProperty(
                    "print_sequence", "value") == "one_at_a_time":
                self._height = min(
                    self._global_container_stack.getProperty(
                        "gantry_height", "value"), machine_height)
                if self._height < machine_height:
                    self._buildVolumeMessage()
            else:
                self._height = self._global_container_stack.getProperty(
                    "machine_height", "value")
            rebuild_me = True

        if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings:
            self._updateDisallowedAreas()
            rebuild_me = True

        if setting_key in self._raft_settings:
            self._updateRaftThickness()
            rebuild_me = True

        if rebuild_me:
            self.rebuild()

    def _updateDisallowedAreas(self):
        if not self._global_container_stack:
            return

        disallowed_areas = copy.deepcopy(
            self._global_container_stack.getProperty(
                "machine_disallowed_areas", "value"))
        areas = []

        machine_width = self._global_container_stack.getProperty(
            "machine_width", "value")
        machine_depth = self._global_container_stack.getProperty(
            "machine_depth", "value")

        # Add prime tower location as disallowed area.
        if self._global_container_stack.getProperty("prime_tower_enable",
                                                    "value") == True:
            prime_tower_size = self._global_container_stack.getProperty(
                "prime_tower_size", "value")
            prime_tower_x = self._global_container_stack.getProperty(
                "prime_tower_position_x", "value") - machine_width / 2
            prime_tower_y = -self._global_container_stack.getProperty(
                "prime_tower_position_y", "value") + machine_depth / 2

            disallowed_areas.append([
                [
                    prime_tower_x - prime_tower_size,
                    prime_tower_y - prime_tower_size
                ],
                [prime_tower_x, prime_tower_y - prime_tower_size],
                [prime_tower_x, prime_tower_y],
                [prime_tower_x - prime_tower_size, prime_tower_y],
            ])

        # Add extruder prime locations as disallowed areas.
        # Probably needs some rework after coordinate system change.
        extruder_manager = ExtruderManager.getInstance()
        extruders = extruder_manager.getMachineExtruders(
            self._global_container_stack.getId())
        for single_extruder in extruders:
            extruder_prime_pos_x = single_extruder.getProperty(
                "extruder_prime_pos_x", "value")
            extruder_prime_pos_y = single_extruder.getProperty(
                "extruder_prime_pos_y", "value")
            # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates.
            # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates
            # (center as origin, y from back to front)
            prime_x = extruder_prime_pos_x - machine_width / 2
            prime_y = machine_depth / 2 - extruder_prime_pos_y
            disallowed_areas.append([
                [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
                [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
                [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
                [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
            ])

        bed_adhesion_size = self._getBedAdhesionSize(
            self._global_container_stack)

        if disallowed_areas:
            # Extend every area already in the disallowed_areas with the skirt size.
            for area in disallowed_areas:
                poly = Polygon(numpy.array(area, numpy.float32))
                poly = poly.getMinkowskiHull(
                    Polygon(approximatedCircleVertices(bed_adhesion_size)))

                areas.append(poly)

        # Add the skirt areas around the borders of the build plate.
        if bed_adhesion_size > 0:
            half_machine_width = self._global_container_stack.getProperty(
                "machine_width", "value") / 2
            half_machine_depth = self._global_container_stack.getProperty(
                "machine_depth", "value") / 2

            areas.append(
                Polygon(
                    numpy.array([[-half_machine_width, -half_machine_depth],
                                 [-half_machine_width, half_machine_depth],
                                 [
                                     -half_machine_width + bed_adhesion_size,
                                     half_machine_depth - bed_adhesion_size
                                 ],
                                 [
                                     -half_machine_width + bed_adhesion_size,
                                     -half_machine_depth + bed_adhesion_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[half_machine_width, half_machine_depth],
                                 [half_machine_width, -half_machine_depth],
                                 [
                                     half_machine_width - bed_adhesion_size,
                                     -half_machine_depth + bed_adhesion_size
                                 ],
                                 [
                                     half_machine_width - bed_adhesion_size,
                                     half_machine_depth - bed_adhesion_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[-half_machine_width, half_machine_depth],
                                 [half_machine_width, half_machine_depth],
                                 [
                                     half_machine_width - bed_adhesion_size,
                                     half_machine_depth - bed_adhesion_size
                                 ],
                                 [
                                     -half_machine_width + bed_adhesion_size,
                                     half_machine_depth - bed_adhesion_size
                                 ]], numpy.float32)))

            areas.append(
                Polygon(
                    numpy.array([[half_machine_width, -half_machine_depth],
                                 [-half_machine_width, -half_machine_depth],
                                 [
                                     -half_machine_width + bed_adhesion_size,
                                     -half_machine_depth + bed_adhesion_size
                                 ],
                                 [
                                     half_machine_width - bed_adhesion_size,
                                     -half_machine_depth + bed_adhesion_size
                                 ]], numpy.float32)))

        self._disallowed_areas = areas

    ##  Convenience function to calculate the size of the bed adhesion in directions x, y.
    def _getBedAdhesionSize(self, container_stack):
        skirt_size = 0.0

        adhesion_type = container_stack.getProperty("adhesion_type", "value")
        if adhesion_type == "skirt":
            skirt_distance = container_stack.getProperty("skirt_gap", "value")
            skirt_line_count = container_stack.getProperty(
                "skirt_line_count", "value")
            skirt_size = skirt_distance + (
                skirt_line_count *
                container_stack.getProperty("skirt_brim_line_width", "value"))
        elif adhesion_type == "brim":
            skirt_size = container_stack.getProperty(
                "brim_line_count", "value") * container_stack.getProperty(
                    "skirt_brim_line_width", "value")
        elif adhesion_type == "raft":
            skirt_size = container_stack.getProperty("raft_margin", "value")

        if container_stack.getProperty("draft_shield_enabled", "value"):
            skirt_size += container_stack.getProperty("draft_shield_dist",
                                                      "value")

        if container_stack.getProperty("xy_offset", "value"):
            skirt_size += container_stack.getProperty("xy_offset", "value")

        return skirt_size

    def _clamp(self, value, min_value, max_value):
        return max(min(value, max_value), min_value)

    _skirt_settings = [
        "adhesion_type", "skirt_gap", "skirt_line_count",
        "skirt_brim_line_width", "brim_width", "brim_line_count",
        "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"
    ]
    _raft_settings = [
        "adhesion_type", "raft_base_thickness", "raft_interface_thickness",
        "raft_surface_layers", "raft_surface_thickness", "raft_airgap"
    ]
    _prime_settings = [
        "extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"
    ]
    _tower_settings = [
        "prime_tower_enable", "prime_tower_size", "prime_tower_position_x",
        "prime_tower_position_y"
    ]
예제 #20
0
class ToolHandle(SceneNode.SceneNode):
    """A tool handle is a object in the scene that gives queues for what the tool it is
    'paired' with can do. ToolHandles are, for example, used for translation, rotation & scale handles.
    They can also be used as actual objects to interact with (in the case of translation,
    pressing one arrow of the toolhandle locks the translation in that direction)
    """

    NoAxis = 1
    XAxis = 2
    YAxis = 3
    ZAxis = 4
    AllAxis = 5

    # These colors are used to draw the selection pass only. They must be unique, which is
    # why we cannot rely on themed colors
    DisabledSelectionColor = Color(0.5, 0.5, 0.5, 1.0)
    XAxisSelectionColor = Color(1.0, 0.0, 0.0, 1.0)
    YAxisSelectionColor = Color(0.0, 0.0, 1.0, 1.0)
    ZAxisSelectionColor = Color(0.0, 1.0, 0.0, 1.0)
    AllAxisSelectionColor = Color(1.0, 1.0, 1.0, 1.0)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._disabled_axis_color = None
        self._x_axis_color = None
        self._y_axis_color = None
        self._z_axis_color = None
        self._all_axis_color = None

        self._axis_color_map = {}

        self._scene = Application.getInstance().getController().getScene()

        self._solid_mesh = None  # type: Optional[MeshData]
        self._line_mesh = None  # type: Optional[MeshData]
        self._selection_mesh = None  # type: Optional[MeshData]
        self._shader = None

        self._active_axis = None  # type: Optional[int]

        # Auto scale is used to ensure that the tool handle will end up the same size on the camera no matter the zoom
        # This should be used to ensure that the tool handles are still usable even if the camera is zoomed in all the way.
        self._auto_scale = True

        self._enabled = False

        self.setCalculateBoundingBox(False)

        Selection.selectionCenterChanged.connect(
            self._onSelectionCenterChanged)
        Application.getInstance().engineCreatedSignal.connect(
            self._onEngineCreated)

    def getLineMesh(self) -> Optional[MeshData]:
        return self._line_mesh

    def setLineMesh(self, mesh: MeshData) -> None:
        self._line_mesh = mesh
        self.meshDataChanged.emit(self)

    def getSolidMesh(self) -> Optional[MeshData]:
        return self._solid_mesh

    def setSolidMesh(self, mesh: MeshData) -> None:
        self._solid_mesh = mesh
        self.meshDataChanged.emit(self)

    def getSelectionMesh(self) -> Optional[MeshData]:
        return self._selection_mesh

    def setSelectionMesh(self, mesh: MeshData) -> None:
        self._selection_mesh = mesh
        self.meshDataChanged.emit(self)

    def render(self, renderer) -> bool:
        if not self._enabled:
            return True

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "toolhandle.shader"))

        if self._auto_scale:
            active_camera = self._scene.getActiveCamera()
            if active_camera.isPerspective():
                camera_position = active_camera.getWorldPosition()
                dist = (camera_position - self.getWorldPosition()).length()
                scale = dist / 400
            else:
                view_width = active_camera.getViewportWidth()
                current_size = view_width + (
                    2 * active_camera.getZoomFactor() * view_width)
                scale = current_size / view_width * 5

            self.setScale(Vector(scale, scale, scale))

        if self._line_mesh:
            renderer.queueNode(self,
                               mesh=self._line_mesh,
                               mode=RenderBatch.RenderMode.Lines,
                               overlay=True,
                               shader=self._shader)
        if self._solid_mesh:
            renderer.queueNode(self,
                               mesh=self._solid_mesh,
                               overlay=True,
                               shader=self._shader)

        return True

    def setActiveAxis(self, axis: Optional[int]) -> None:
        if axis == self._active_axis or not self._shader:
            return

        if axis:
            self._shader.setUniformValue(
                "u_activeColor", self._axis_color_map.get(axis, Color()))
        else:
            self._shader.setUniformValue("u_activeColor",
                                         self._disabled_axis_color)
        self._active_axis = axis
        self._scene.sceneChanged.emit(self)

    def getActiveAxis(self) -> Optional[int]:
        return self._active_axis

    def isAxis(self, value):
        return value in self._axis_color_map

    def buildMesh(self) -> None:
        # This method should be overridden by toolhandle implementations
        pass

    def _onSelectionCenterChanged(self) -> None:
        if self._enabled:
            self.setPosition(Selection.getSelectionCenter())

    def setEnabled(self, enable: bool):
        super().setEnabled(enable)
        # Force an update
        self._onSelectionCenterChanged()

    def _onEngineCreated(self) -> None:
        from UM.Qt.QtApplication import QtApplication
        theme = QtApplication.getInstance().getTheme()
        if theme is None:
            Logger.log(
                "w",
                "Could not get theme, so unable to create tool handle meshes.")
            return
        self._disabled_axis_color = Color(
            *theme.getColor("disabled_axis").getRgb())
        self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
        self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
        self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
        self._all_axis_color = Color(*theme.getColor("all_axis").getRgb())

        self._axis_color_map = {
            self.NoAxis: self._disabled_axis_color,
            self.XAxis: self._x_axis_color,
            self.YAxis: self._y_axis_color,
            self.ZAxis: self._z_axis_color,
            self.AllAxis: self._all_axis_color
        }

        self.buildMesh()
예제 #21
0
    def render(self):
        if not self._layer_shader:
            if self._compatibility_mode:
                shader_filename = "layers.shader"
                shadow_shader_filename = "layers_shadow.shader"
            else:
                shader_filename = "layers3d.shader"
                shadow_shader_filename = "layers3d_shadow.shader"
            self._layer_shader = OpenGL.getInstance().createShaderProgram(
                os.path.join(
                    PluginRegistry.getInstance().getPluginPath(
                        "SimulationView"), shader_filename))
            self._layer_shadow_shader = OpenGL.getInstance(
            ).createShaderProgram(
                os.path.join(
                    PluginRegistry.getInstance().getPluginPath(
                        "SimulationView"), shadow_shader_filename))
            self._current_shader = self._layer_shader
        # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
        self._layer_shader.setUniformValue(
            "u_active_extruder",
            float(max(0, self._extruder_manager.activeExtruderIndex)))
        if self._layer_view:
            self._layer_shader.setUniformValue(
                "u_max_feedrate", self._layer_view.getMaxFeedrate())
            self._layer_shader.setUniformValue(
                "u_min_feedrate", self._layer_view.getMinFeedrate())
            self._layer_shader.setUniformValue(
                "u_max_thickness", self._layer_view.getMaxThickness())
            self._layer_shader.setUniformValue(
                "u_min_thickness", self._layer_view.getMinThickness())
            self._layer_shader.setUniformValue(
                "u_layer_view_type", self._layer_view.getSimulationViewType())
            self._layer_shader.setUniformValue(
                "u_extruder_opacity", self._layer_view.getExtruderOpacities())
            self._layer_shader.setUniformValue(
                "u_show_travel_moves", self._layer_view.getShowTravelMoves())
            self._layer_shader.setUniformValue(
                "u_show_helpers", self._layer_view.getShowHelpers())
            self._layer_shader.setUniformValue("u_show_skin",
                                               self._layer_view.getShowSkin())
            self._layer_shader.setUniformValue(
                "u_show_infill", self._layer_view.getShowInfill())
        else:
            #defaults
            self._layer_shader.setUniformValue("u_max_feedrate", 1)
            self._layer_shader.setUniformValue("u_min_feedrate", 0)
            self._layer_shader.setUniformValue("u_max_thickness", 1)
            self._layer_shader.setUniformValue("u_min_thickness", 0)
            self._layer_shader.setUniformValue("u_layer_view_type", 1)
            self._layer_shader.setUniformValue(
                "u_extruder_opacity",
                [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]])
            self._layer_shader.setUniformValue("u_show_travel_moves", 0)
            self._layer_shader.setUniformValue("u_show_helpers", 1)
            self._layer_shader.setUniformValue("u_show_skin", 1)
            self._layer_shader.setUniformValue("u_show_infill", 1)

        if not self._tool_handle_shader:
            self._tool_handle_shader = OpenGL.getInstance(
            ).createShaderProgram(
                Resources.getPath(Resources.Shaders, "toolhandle.shader"))

        if not self._nozzle_shader:
            self._nozzle_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "color.shader"))
            self._nozzle_shader.setUniformValue(
                "u_color",
                Color(*Application.getInstance().getTheme().getColor(
                    "layerview_nozzle").getRgb()))

        if not self._disabled_shader:
            self._disabled_shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "striped.shader"))
            self._disabled_shader.setUniformValue(
                "u_diffuseColor1",
                Color(*Application.getInstance().getTheme().getColor(
                    "model_unslicable").getRgb()))
            self._disabled_shader.setUniformValue(
                "u_diffuseColor2",
                Color(*Application.getInstance().getTheme().getColor(
                    "model_unslicable_alt").getRgb()))
            self._disabled_shader.setUniformValue("u_width", 50.0)
            self._disabled_shader.setUniformValue("u_opacity", 0.6)

        self.bind()

        tool_handle_batch = RenderBatch(self._tool_handle_shader,
                                        type=RenderBatch.RenderType.Overlay,
                                        backface_cull=True)
        disabled_batch = RenderBatch(self._disabled_shader)
        head_position = None  # Indicates the current position of the print head
        nozzle_node = None

        for node in DepthFirstIterator(self._scene.getRoot()):

            if isinstance(node, ToolHandle):
                tool_handle_batch.addItem(node.getWorldTransformation(),
                                          mesh=node.getSolidMesh())

            elif isinstance(node, NozzleNode):
                nozzle_node = node
                nozzle_node.setVisible(
                    False)  # Don't set to true, we render it separately!

            elif getattr(node, "_outside_buildarea", False) and isinstance(
                    node,
                    SceneNode) and node.getMeshData() and node.isVisible():
                disabled_batch.addItem(node.getWorldTransformation(copy=False),
                                       node.getMeshData())

            elif isinstance(node, SceneNode) and (node.getMeshData(
            ) or node.callDecoration("isBlockSlicing")) and node.isVisible():
                layer_data = node.callDecoration("getLayerData")
                if not layer_data:
                    continue

                # Render all layers below a certain number as line mesh instead of vertices.
                if self._layer_view._current_layer_num > -1 and (
                    (not self._layer_view._only_show_top_layers) or
                    (not self._layer_view.getCompatibilityMode())):
                    start = 0
                    end = 0
                    element_counts = layer_data.getElementCounts()
                    for layer in sorted(element_counts.keys()):
                        # In the current layer, we show just the indicated paths
                        if layer == self._layer_view._current_layer_num:
                            # We look for the position of the head, searching the point of the current path
                            index = self._layer_view._current_path_num
                            offset = 0
                            for polygon in layer_data.getLayer(layer).polygons:
                                # The size indicates all values in the two-dimension array, and the second dimension is
                                # always size 3 because we have 3D points.
                                if index >= polygon.data.size // 3 - offset:
                                    index -= polygon.data.size // 3 - offset
                                    offset = 1  # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
                                    continue
                                # The head position is calculated and translated
                                head_position = Vector(
                                    polygon.data[index + offset][0],
                                    polygon.data[index + offset][1],
                                    polygon.data[index + offset]
                                    [2]) + node.getWorldPosition()
                                break
                            break
                        if self._layer_view._minimum_layer_num > layer:
                            start += element_counts[layer]
                        end += element_counts[layer]

                    # Calculate the range of paths in the last layer
                    current_layer_start = end
                    current_layer_end = end + self._layer_view._current_path_num * 2  # Because each point is used twice

                    # This uses glDrawRangeElements internally to only draw a certain range of lines.
                    # All the layers but the current selected layer are rendered first
                    if self._old_current_path != self._layer_view._current_path_num:
                        self._current_shader = self._layer_shadow_shader
                        self._switching_layers = False
                    if not self._layer_view.isSimulationRunning(
                    ) and self._old_current_layer != self._layer_view._current_layer_num:
                        self._current_shader = self._layer_shader
                        self._switching_layers = True

                    layers_batch = RenderBatch(
                        self._current_shader,
                        type=RenderBatch.RenderType.Solid,
                        mode=RenderBatch.RenderMode.Lines,
                        range=(start, end),
                        backface_cull=True)
                    layers_batch.addItem(node.getWorldTransformation(),
                                         layer_data)
                    layers_batch.render(self._scene.getActiveCamera())

                    # Current selected layer is rendered
                    current_layer_batch = RenderBatch(
                        self._layer_shader,
                        type=RenderBatch.RenderType.Solid,
                        mode=RenderBatch.RenderMode.Lines,
                        range=(current_layer_start, current_layer_end))
                    current_layer_batch.addItem(node.getWorldTransformation(),
                                                layer_data)
                    current_layer_batch.render(self._scene.getActiveCamera())

                    self._old_current_layer = self._layer_view._current_layer_num
                    self._old_current_path = self._layer_view._current_path_num

                # Create a new batch that is not range-limited
                batch = RenderBatch(self._layer_shader,
                                    type=RenderBatch.RenderType.Solid)

                if self._layer_view.getCurrentLayerMesh():
                    batch.addItem(node.getWorldTransformation(),
                                  self._layer_view.getCurrentLayerMesh())

                if self._layer_view.getCurrentLayerJumps():
                    batch.addItem(node.getWorldTransformation(),
                                  self._layer_view.getCurrentLayerJumps())

                if len(batch.items) > 0:
                    batch.render(self._scene.getActiveCamera())

        # The nozzle is drawn when once we know the correct position of the head,
        # but the user is not using the layer slider, and the compatibility mode is not enabled
        if not self._switching_layers and not self._compatibility_mode and self._layer_view.getActivity(
        ) and nozzle_node is not None:
            if head_position is not None:
                nozzle_node.setPosition(head_position)
                nozzle_batch = RenderBatch(
                    self._nozzle_shader,
                    type=RenderBatch.RenderType.Transparent)
                nozzle_batch.addItem(nozzle_node.getWorldTransformation(),
                                     mesh=nozzle_node.getMeshData())
                nozzle_batch.render(self._scene.getActiveCamera())

        if len(disabled_batch.items) > 0:
            disabled_batch.render(self._scene.getActiveCamera())

        # Render toolhandles on top of the layerview
        if len(tool_handle_batch.items) > 0:
            tool_handle_batch.render(self._scene.getActiveCamera())

        self.release()
예제 #22
0
def test_fromHexString(data):
    color = Color.fromHexString(data["data_to_set"])
    expected_color = Color(*data["expected"])
    assert color == expected_color