def run(self): layer_data = None for node in DepthFirstIterator(self._scene.getRoot()): layer_data = node.callDecoration("getLayerData") if layer_data: break if self._cancel or not layer_data: return layer_mesh = MeshData() for i in range(self._solid_layers): layer_number = self._layer_number - i if layer_number < 0: continue try: layer = layer_data.getLayer(layer_number).createMesh() except Exception as e: print(e) return if not layer or layer.getVertices() is None: continue layer_mesh.addVertices(layer.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 layer_mesh.addColors(layer.getColors() * brightness) if self._cancel: return Job.yieldThread() if self._cancel: return Job.yieldThread() jump_mesh = layer_data.getLayer(self._layer_number).createJumps() if not jump_mesh or jump_mesh.getVertices() is None: jump_mesh = None self.setResult({"layers": layer_mesh, "jumps": jump_mesh})
def run(self): layer_data = None for node in DepthFirstIterator(self._scene.getRoot()): layer_data = node.callDecoration("getLayerData") if layer_data: break if self._cancel or not layer_data: return layer_mesh = MeshData() for i in range(self._solid_layers): layer_number = self._layer_number - i if layer_number < 0: continue try: layer = layer_data.getLayer(layer_number).createMesh() except Exception as e: print(e) return if not layer or layer.getVertices() is None: continue layer_mesh.addVertices(layer.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 layer_mesh.addColors(layer.getColors() * brightness) if self._cancel: return Job.yieldThread() if self._cancel: return Job.yieldThread() jump_mesh = layer_data.getLayer(self._layer_number).createJumps() if not jump_mesh or jump_mesh.getVertices() is None: jump_mesh = None self.setResult({ "layers": layer_mesh, "jumps": jump_mesh })
class LayerView(View): def __init__(self): super().__init__() self._material = None self._num_layers = 0 self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100) self._proxy = LayerViewProxy.LayerViewProxy() self._controller.getScene().sceneChanged.connect(self._onSceneChanged) self._max_layers = 10 self._current_layer_num = 10 self._current_layer_mesh = None self._activity = False self._solid_layers = 5 def getActivity(self): return self._activity def getCurrentLayer(self): return self._current_layer_num def _onSceneChanged(self, node): self.calculateMaxLayers() def getMaxLayers(self): return self._max_layers def resetLayerData(self): self._current_layer_mesh = None def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() renderer.setRenderSelection(False) if not self._material: self._material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "vertexcolor.frag")) self._material.setUniformValue("u_color", [1.0, 0.0, 0.0, 1.0]) self._selection_material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "color.frag")) self._selection_material.setUniformValue("u_color", Color(35, 35, 35, 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, material = self._selection_material, transparent = True) 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, material = self._material, mode = Renderer.RenderLines, start = start, end = 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 layer_mesh = layer_data.getLayer(layer).createMesh() if not layer_mesh or layer_mesh.getVertices() is None: continue 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 self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness) renderer.queueNode(node, mesh = self._current_layer_mesh, material = self._material) def setLayer(self, value): if self._current_layer_num != value: self._current_layer_num = value if self._current_layer_num < 0: self._current_layer_num = 0 if self._current_layer_num > self._max_layers: self._current_layer_num = self._max_layers self._current_layer_mesh = None self.currentLayerNumChanged.emit() currentLayerNumChanged = Signal() def calculateMaxLayers(self): scene = self.getController().getScene() renderer = self.getRenderer() if renderer and self._material: self._activity = True renderer.setRenderSelection(False) self._old_max_layers = self._max_layers ## Recalculate num max layers new_max_layers = 0 for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): if node.getMeshData() and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: continue if new_max_layers < len(layer_data.getLayers()): new_max_layers = len(layer_data.getLayers()) - 1 if new_max_layers > 0 and new_max_layers != self._old_max_layers: self._max_layers = new_max_layers self.maxLayersChanged.emit() self._current_layer_num = self._max_layers # This makes sure we update the current layer self.setLayer(int(self._max_layers)) self.currentLayerNumChanged.emit() maxLayersChanged = Signal() currentLayerNumChanged = Signal() ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created # as this caused some issues. def getProxy(self, engine, script_engine): return self._proxy def endRendering(self): pass def event(self, event): if event.type == Event.KeyPressEvent: if event.key == KeyEvent.UpKey: self.setLayer(self._current_layer_num + 1) if event.key == KeyEvent.DownKey: self.setLayer(self._current_layer_num - 1)
class MeshBuilder: def __init__(self): self._mesh_data = MeshData() def getData(self): return self._mesh_data def addLine(self, v0, v1, **kwargs): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addFace(self, v0, v1, v2, **kwargs): normal = kwargs.get("normal", None) if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addQuad(self, v0, v1, v2, v3, **kwargs): self.addFace(v0, v2, v1, color = kwargs.get("color"), normal = kwargs.get("normal") ) self.addFace(v0, v3, v2, color = kwargs.get("color"), normal = kwargs.get("normal") ) def addCube(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) def addArc(self, **kwargs): radius = kwargs["radius"] axis = kwargs["axis"] max_angle = kwargs.get("angle", math.pi * 2) center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = max_angle / sections angle = 0 point = start + center m = Matrix() while angle <= max_angle: self._mesh_data.addVertex(point.x, point.y, point.z) angle += angle_increment m.setByRotationAxis(angle, axis) point = start.multiply(m) + center self._mesh_data.addVertex(point.x, point.y, point.z) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addDonut(self, **kwargs): inner_radius = kwargs["inner_radius"] outer_radius = kwargs["outer_radius"] width = kwargs["width"] center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) angle = kwargs.get("angle", 0) axis = kwargs.get("axis", Vector.Unit_Y) vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() for i in range(sections): v1 = start + i * 3 v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) c = math.cos(theta) s = math.sin(theta) vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3,0:3]) vertices[:] += center.getData() self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) def addPyramid(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] angle = math.radians(kwargs.get("angle", 0)) axis = kwargs.get("axis", Vector.Unit_Y) center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 1, start + 4], [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)
class LayerView(View): def __init__(self): super().__init__() self._shader = None self._selection_shader = None self._num_layers = 0 self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100) self._proxy = LayerViewProxy.LayerViewProxy() self._controller.getScene().sceneChanged.connect(self._onSceneChanged) self._max_layers = 10 self._current_layer_num = 10 self._current_layer_mesh = None self._current_layer_jumps = None self._activity = False self._solid_layers = 5 def getActivity(self): return self._activity def getCurrentLayer(self): return self._current_layer_num def _onSceneChanged(self, node): self.calculateMaxLayers() def getMaxLayers(self): return self._max_layers def resetLayerData(self): self._current_layer_mesh = None self._current_layer_jumps = None 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, "color.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) def setLayer(self, value): if self._current_layer_num != value: self._current_layer_num = value if self._current_layer_num < 0: self._current_layer_num = 0 if self._current_layer_num > self._max_layers: self._current_layer_num = self._max_layers self._current_layer_mesh = None self._current_layer_jumps = None self.currentLayerNumChanged.emit() currentLayerNumChanged = Signal() def calculateMaxLayers(self): scene = self.getController().getScene() renderer = self.getRenderer() self._activity = True self._old_max_layers = self._max_layers ## Recalculate num max layers new_max_layers = 0 for node in DepthFirstIterator(scene.getRoot()): layer_data = node.callDecoration("getLayerData") if not layer_data: continue if new_max_layers < len(layer_data.getLayers()): new_max_layers = len(layer_data.getLayers()) - 1 if new_max_layers > 0 and new_max_layers != self._old_max_layers: self._max_layers = new_max_layers # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first # if it's the largest value. If we don't do this, we can have a slider block outside of the # slider. if new_max_layers > self._current_layer_num: self.maxLayersChanged.emit() self.setLayer(int(self._max_layers)) else: self.setLayer(int(self._max_layers)) self.maxLayersChanged.emit() maxLayersChanged = Signal() currentLayerNumChanged = Signal() ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created # as this caused some issues. def getProxy(self, engine, script_engine): return self._proxy def endRendering(self): pass def event(self, event): modifiers = QtWidgets.QApplication.keyboardModifiers() ctrl_is_active = modifiers == QtCore.Qt.ControlModifier if event.type == Event.KeyPressEvent and ctrl_is_active: if event.key == KeyEvent.UpKey: self.setLayer(self._current_layer_num + 1) return True if event.key == KeyEvent.DownKey: self.setLayer(self._current_layer_num - 1) return True
class MeshBuilder: ## Creates a new MeshBuilder with an empty mesh. def __init__(self): self._mesh_data = MeshData() ## Gets the mesh that was built by this MeshBuilder. # # Note that this gets a reference to the mesh. Adding more primitives to # the MeshBuilder will cause the mesh returned by this function to change # as well. # # \return A mesh with all the primitives added to this MeshBuilder. def getData(self): return self._mesh_data ## Adds a 3-dimensional line to the mesh of this mesh builder. # # \param v0 One endpoint of the line to add. # \param v1 The other endpoint of the line to add. # \param color (Optional) The colour of the line, if any. If no colour is # provided, the colour is determined by the shader. def addLine(self, v0, v1, color = None): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) if color: #Add colours to the vertices, if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a triangle to the mesh of this mesh builder. # # \param v0 The first corner of the triangle. # \param v1 The second corner of the triangle. # \param v2 The third corner of the triangle. # \param normal (Optional) The normal vector for the triangle. If no # normal vector is provided, it will be calculated automatically. # \param color (Optional) The colour for the triangle. If no colour is # provided, the colour is determined by the shader. def addFace(self, v0, v1, v2, normal = None, color = None): if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) #Computes the normal by itself. if color: #Add colours to the vertices if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Add a quadrilateral to the mesh of this mesh builder. # # The quadrilateral will be constructed as two triangles. v0 and v2 are # the two vertices across the diagonal of the quadrilateral. # # \param v0 The first corner of the quadrilateral. # \param v1 The second corner of the quadrilateral. # \param v2 The third corner of the quadrilateral. # \param v3 The fourth corner of the quadrilateral. # \param normal (Optional) The normal vector for the quadrilateral. Both # triangles will get the same normal vector, if provided. If no normal # vector is provided, the normal vectors for both triangles are computed # automatically. # \param color (Optional) The colour for the quadrilateral. If no colour # is provided, the colour is determined by the shader. def addQuad(self, v0, v1, v2, v3, normal = None, color = None): self.addFace(v0, v2, v1, color = color, normal = normal ) self.addFace(v0, v3, v2, #v0 and v2 are shared with the other triangle! color = color, normal = normal ) ## Add a rectangular cuboid to the mesh of this mesh builder. # # A rectangular cuboid is a square block with arbitrary width, height and # depth. # # \param width The size of the rectangular cuboid in the X dimension. # \param height The size of the rectangular cuboid in the Y dimension. # \param depth The size of the rectangular cuboid in the Z dimension. # \param center (Optional) The position of the centre of the rectangular # cuboid in space. If not provided, the cuboid is placed at the coordinate # origin. # \param color (Optional) The colour for the rectangular cuboid. If no # colour is provided, the colour is determined by the shader. def addCube(self, width, height, depth, center = Vector(0, 0, 0), color = None): #Compute the actual positions of the planes. minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ #All 8 corners. [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ #All 6 quads (12 triangles). [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add a colour to all of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) ## Add an arc to the mesh of this mesh builder. # # An arc is a curve that is also a segment of a circle. # # \param radius The radius of the circle this arc is a segment of. # \param axis The axis perpendicular to the plane on which the arc lies. # \param angle (Optional) The length of the arc, in radians. If not # provided, the entire circle is used (2 pi). # \param center (Optional) The position of the centre of the arc in space. # If no position is provided, the arc is centred around the coordinate # origin. # \param sections (Optional) The resolution of the arc. The arc is # approximated by this number of line segments. # \param color (Optional) The colour for the arc. If no colour is # provided, the colour is determined by the shader. def addArc(self, radius, axis, angle = math.pi * 2, center = Vector(0, 0, 0), sections = 32, color = None): #We'll compute the vertices of the arc by computing an initial point and #rotating the initial point with a rotation matrix. if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = angle / sections current_angle = 0 point = start + center m = Matrix() while current_angle <= angle: #Add each of the vertices. self._mesh_data.addVertex(point.x, point.y, point.z) current_angle += angle_increment m.setByRotationAxis(current_angle, axis) point = start.multiply(m) + center #Get the next vertex by rotating the start position with a matrix. self._mesh_data.addVertex(point.x, point.y, point.z) if color: #If we have a colour, add that colour to the new vertex. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a torus to the mesh of this mesh builder. # # The torus is the shape of a doughnut. This doughnut is delicious and # moist, but not very healthy. # # \param inner_radius The radius of the hole inside the torus. Must be # smaller than outer_radius. # \param outer_radius The radius of the outside of the torus. Must be # larger than inner_radius. # \param width The radius of the torus in perpendicular direction to its # perimeter. This is the "thickness". # \param center (Optional) The position of the centre of the torus. If no # position is provided, the torus will be centred around the coordinate # origin. # \param sections (Optional) The resolution of the torus in the # circumference. The resolution of the intersection of the torus cannot be # changed. # \param color (Optional) The colour of the torus. If no colour is # provided, a colour will be determined by the shader. # \param angle (Optional) An angle of rotation to rotate the torus by, in # radians. # \param axis (Optional) An axis of rotation to rotate the torus around. # If no axis is provided and the angle of rotation is nonzero, the torus # will be rotated around the Y-axis. def addDonut(self, inner_radius, outer_radius, width, center = Vector(0, 0, 0), sections = 32, color = None, angle = 0, axis = Vector.Unit_Y): vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() #Starting index. for i in range(sections): v1 = start + i * 3 #Indices for each of the vertices we'll add for this section. v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) #Angle of this piece around torus perimeter. c = math.cos(theta) #X-coordinate around torus perimeter. s = math.sin(theta) #Y-coordinate around torus perimeter. #One vertex on the inside perimeter, two on the outside perimiter (up and down). vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) #Connect the vertices to the next segment. indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: #If we have a colour, add it to the vertices. colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) #Rotate the resulting torus around the specified axis. matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3, 0:3]) vertices[:] += center.getData() #And translate to the desired position. self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) ## Adds a pyramid to the mesh of this mesh builder. # # \param width The width of the base of the pyramid. # \param height The height of the pyramid (from base to notch). # \param depth The depth of the base of the pyramid. # \param angle (Optional) An angle of rotation to rotate the pyramid by, # in degrees. # \param axis (Optional) An axis of rotation to rotate the pyramid around. # If no axis is provided and the angle of rotation is nonzero, the pyramid # will be rotated around the Y-axis. # \param center (Optional) The position of the centre of the base of the # pyramid. If not provided, the pyramid will be placed on the coordinate # origin. # \param color (Optional) The colour of the pyramid. If no colour is # provided, a colour will be determined by the shader. def addPyramid(self, width, height, depth, angle = 0, axis = Vector.Unit_Y, center = Vector(0, 0, 0), color = None): angle = math.radians(angle) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() #Starting index. matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ #All 5 vertices of the pyramid. [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) #Rotate the pyramid around the axis. verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ #Connect the vertices to each other (6 triangles). [start, start + 1, start + 4], #The four sides of the pyramid. [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], #The base of the pyramid. [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add the colour to each of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)
class LayerView(View): def __init__(self): super().__init__() self._material = None self._num_layers = 0 self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100) self._proxy = LayerViewProxy.LayerViewProxy() self._controller.getScene().sceneChanged.connect(self._onSceneChanged) self._max_layers = 10 self._current_layer_num = 10 self._current_layer_mesh = None self._current_layer_jumps = None self._activity = False self._solid_layers = 5 def getActivity(self): return self._activity def getCurrentLayer(self): return self._current_layer_num def _onSceneChanged(self, node): self.calculateMaxLayers() def getMaxLayers(self): return self._max_layers def resetLayerData(self): self._current_layer_mesh = None self._current_layer_jumps = None def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() renderer.setRenderSelection(False) if not self._material: self._material = renderer.createMaterial( Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "vertexcolor.frag")) self._material.setUniformValue("u_color", [1.0, 0.0, 0.0, 1.0]) self._selection_material = renderer.createMaterial( Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "color.frag")) self._selection_material.setUniformValue("u_color", Color(35, 35, 35, 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, material=self._selection_material, transparent=True) 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, material=self._material, mode=Renderer.RenderLines, start=start, end=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, material=self._material) 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, material=self._material) def setLayer(self, value): if self._current_layer_num != value: self._current_layer_num = value if self._current_layer_num < 0: self._current_layer_num = 0 if self._current_layer_num > self._max_layers: self._current_layer_num = self._max_layers self._current_layer_mesh = None self._current_layer_jumps = None self.currentLayerNumChanged.emit() currentLayerNumChanged = Signal() def calculateMaxLayers(self): scene = self.getController().getScene() renderer = self.getRenderer() if renderer and self._material: self._activity = True renderer.setRenderSelection(False) self._old_max_layers = self._max_layers ## Recalculate num max layers new_max_layers = 0 for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): if node.getMeshData() and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: continue if new_max_layers < len(layer_data.getLayers()): new_max_layers = len(layer_data.getLayers()) - 1 if new_max_layers > 0 and new_max_layers != self._old_max_layers: self._max_layers = new_max_layers self.maxLayersChanged.emit() self._current_layer_num = self._max_layers # This makes sure we update the current layer self.setLayer(int(self._max_layers)) self.currentLayerNumChanged.emit() maxLayersChanged = Signal() currentLayerNumChanged = Signal() ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created # as this caused some issues. def getProxy(self, engine, script_engine): return self._proxy def endRendering(self): pass def event(self, event): modifiers = QtWidgets.QApplication.keyboardModifiers() ctrl_is_active = modifiers == QtCore.Qt.ControlModifier if event.type == Event.KeyPressEvent and ctrl_is_active: if event.key == KeyEvent.UpKey: self.setLayer(self._current_layer_num + 1) return True if event.key == KeyEvent.DownKey: self.setLayer(self._current_layer_num - 1) return True