def read(self, file_name): mesh_builder = MeshBuilder() scene_node = SceneNode() self.load_file(file_name, mesh_builder, _use_numpystl = use_numpystl) mesh = mesh_builder.build() if use_numpystl: verts = mesh.getVertices() # In some cases numpy stl reads incorrectly and the result is that the Z values are all 0 # Add new error cases if you find them. if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]): # Something may have gone wrong in numpy stl, start over without numpy stl Logger.log("w", "All Z coordinates are the same using numpystl, trying again without numpy stl.") mesh_builder = MeshBuilder() self.load_file(file_name, mesh_builder, _use_numpystl = False) mesh = mesh_builder.build() verts = mesh.getVertices() if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]): Logger.log("e", "All Z coordinates are still the same without numpy stl... let's hope for the best") if mesh_builder.getVertexCount() == 0: Logger.log("d", "File did not contain valid data, unable to read.") return None # We didn't load anything. scene_node.setMeshData(mesh) Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount()) return scene_node
def buildMesh(self): #SOLIDMESH mb = MeshBuilder() mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, color = self._z_axis_color ) mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, axis = Vector.Unit_X, angle = math.pi / 2, color = self._y_axis_color ) mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, axis = Vector.Unit_Y, angle = math.pi / 2, color = self._x_axis_color ) self.setSolidMesh(mb.build()) #SELECTIONMESH mb = MeshBuilder() mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, color = ToolHandle.ZAxisSelectionColor ) mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, axis = Vector.Unit_X, angle = math.pi / 2, color = ToolHandle.YAxisSelectionColor ) mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, axis = Vector.Unit_Y, angle = math.pi / 2, color = ToolHandle.XAxisSelectionColor ) self.setSelectionMesh(mb.build())
def _createEraserMesh(self, parent: CuraSceneNode, position: Vector): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) mesh = MeshBuilder() mesh.addCube(10,10,10) node.setMeshData(mesh.build()) active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode settings = stack.getTop() definition = stack.getSettingDefinition("anti_overhang_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState() # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) op = GroupedOperation() # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot())) op.addOperation(SetParentOperation(node, parent)) op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) Application.getInstance().getController().getScene().sceneChanged.emit(node)
def test_render(): mocked_shader = MagicMock() with patch("UM.View.GL.OpenGL.OpenGL.getInstance"): render_batch = RenderBatch(mocked_shader) # Render without a camera shouldn't cause any effect. render_batch.render(None) assert mocked_shader.bind.call_count == 0 # Rendering with a camera should cause the shader to be bound and released (even if the batch is empty) mocked_camera = MagicMock() mocked_camera.getWorldTransformation = MagicMock(return_value = Matrix()) mocked_camera.getViewProjectionMatrix = MagicMock(return_value=Matrix()) with patch("UM.View.GL.OpenGLContext.OpenGLContext.properties"): render_batch.render(mocked_camera) assert mocked_shader.bind.call_count == 1 assert mocked_shader.release.call_count == 1 # Actualy render with an item in the batch mb = MeshBuilder() mb.addPyramid(10, 10, 10, color=Color(0.0, 1.0, 0.0, 1.0)) mb.calculateNormals() mesh_data = mb.build() render_batch.addItem(Matrix(), mesh_data, {}) with patch("UM.View.GL.OpenGL.OpenGL.getInstance"): with patch("UM.View.GL.OpenGLContext.OpenGLContext.properties"): render_batch.render(mocked_camera) assert mocked_shader.bind.call_count == 2 assert mocked_shader.release.call_count == 2
def __init__(self, node, hull, thickness, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._original_parent = parent # Color of the drawn convex hull if Application.getInstance().hasGui(): self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb()) else: self._color = Color(0, 0, 0) # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 self._thickness = thickness # The node this mesh is "watching" self._node = node self._convex_hull_head_mesh = None self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() if hull_mesh_builder.addConvexPolygonExtrusion( self._hull.getPoints()[::-1], # bottom layer is reversed self._mesh_height-thickness, self._mesh_height, color=self._color): hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh)
def read(self, file_name): mesh_builder = MeshBuilder() scene_node = SceneNode() if use_numpystl: self._loadWithNumpySTL(file_name, mesh_builder) else: f = open(file_name, "rb") if not self._loadBinary(mesh_builder, f): f.close() f = open(file_name, "rt") try: self._loadAscii(mesh_builder, f) except UnicodeDecodeError: return None f.close() Job.yieldThread() # Yield somewhat to ensure the GUI has time to update a bit. mesh_builder.calculateNormals(fast = True) mesh = mesh_builder.build() Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount()) scene_node.setMeshData(mesh) return scene_node
def _onNodeDecoratorsChanged(self, node): convex_hull_head = self._node.callDecoration("getConvexHullHead") if convex_hull_head: convex_hull_head_builder = MeshBuilder() convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height - self._thickness) self._convex_hull_head_mesh = convex_hull_head_builder.build() if not node: return
def read(self, file_name): try: self.defs = {} self.shapes = [] tree = ET.parse(file_name) xml_root = tree.getroot() if xml_root.tag != "X3D": return None scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters if xml_root[0].tag == "head": for head_node in xml_root[0]: if head_node.tag == "unit" and head_node.attrib.get("category") == "length": scale *= float(head_node.attrib["conversionFactor"]) break xml_scene = xml_root[1] else: xml_scene = xml_root[0] if xml_scene.tag != "Scene": return None self.transform = Matrix() self.transform.setByScaleFactor(scale) self.index_base = 0 # Traverse the scene tree, populate the shapes list self.processChildNodes(xml_scene) if self.shapes: builder = MeshBuilder() builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes])) builder.setIndices(numpy.concatenate([shape.faces for shape in self.shapes])) builder.calculateNormals() builder.setFileName(file_name) mesh_data = builder.build() # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from # leaving the reader. mesh_data.getExtents() node = SceneNode() node.setMeshData(mesh_data) node.setSelectable(True) node.setName(file_name) else: return None except Exception: Logger.logException("e", "Exception in X3D reader") return None return node
def test_compute2DConvexHullMeshData(convex_hull_decorator): node = SceneNode() mb = MeshBuilder() mb.addCube(10,10,10) node.setMeshData(mb.build()) convex_hull_decorator._getSettingProperty = MagicMock(return_value = 0) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) assert convex_hull_decorator._compute2DConvexHull() == Polygon([[5.0,-5.0], [-5.0,-5.0], [-5.0,5.0], [5.0,5.0]])
def test_getExtents(): # Create a cube mesh at position 0,0,0 builder = MeshBuilder() builder.addCube(20, 20, 20) mesh_data = builder.build() extents = mesh_data.getExtents() assert extents.width == 20 assert extents.height == 20 assert extents.depth == 20 assert extents.maximum == Vector(10, 10, 10) assert extents.minimum == Vector(-10, -10, -10)
def createHullMesh(self, hull_points): # Input checking. if len(hull_points) < 3: return None mesh_builder = MeshBuilder() point_first = Vector(hull_points[0][0], self._mesh_height, hull_points[0][1]) point_previous = Vector(hull_points[1][0], self._mesh_height, hull_points[1][1]) for point in hull_points[2:]: # Add the faces in the order of a triangle fan. point_new = Vector(point[0], self._mesh_height, point[1]) mesh_builder.addFace(point_first, point_previous, point_new, color = self._color) point_previous = point_new # Prepare point_previous for the next triangle. return mesh_builder.build()
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 = MeshBuilder() 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: Logger.logException("w", "An exception occurred while creating layer mesh.") return if not layer or layer.getVertices() is None: continue layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices()) 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 = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 brightness[0, 3] = 1.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.build(), "jumps": jump_mesh})
def test_getExtentsTransposed(): # Create a cube mesh at position 0,0,0 builder = MeshBuilder() builder.addCube(20, 20, 20) mesh_data = builder.build() transformation_matrix = Matrix() transformation_matrix.setByTranslation(Vector(10, 10, 10)) extents = mesh_data.getExtents(transformation_matrix) assert extents.width == 20 assert extents.height == 20 assert extents.depth == 20 assert extents.maximum == Vector(20, 20, 20) assert extents.minimum == Vector(0, 0, 0)
def createMeshOrJumps(self, make_mesh): builder = MeshBuilder() line_count = 0 if make_mesh: for polygon in self._polygons: line_count += polygon.meshLineCount else: for polygon in self._polygons: line_count += polygon.jumpCount # Reserve the neccesary space for the data upfront builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count) for polygon in self._polygons: # Filter out the types of lines we are not interesed in depending on whether we are drawing the mesh or the jumps. index_mask = numpy.logical_not(polygon.jumpMask) if make_mesh else polygon.jumpMask # Create an array with rows [p p+1] and only keep those we whant to draw based on make_mesh points = numpy.concatenate((polygon.data[:-1], polygon.data[1:]), 1)[index_mask.ravel()] # Line types of the points we want to draw line_types = polygon.types[index_mask] # Shift the z-axis according to previous implementation. if make_mesh: points[polygon.isInfillOrSkinType(line_types), 1::3] -= 0.01 else: points[:, 1::3] += 0.01 # Create an array with normals and tile 2 copies to match size of points variable normals = numpy.tile( polygon.getNormals()[index_mask.ravel()], (1, 2)) # Scale all normals by the line width of the current line so we can easily offset. normals *= (polygon.lineWidths[index_mask.ravel()] / 2) # Create 4 points to draw each line segment, points +- normals results in 2 points each. # After this we reshape to one point per line. f_points = numpy.concatenate((points-normals, points+normals), 1).reshape((-1, 3)) # __index_pattern defines which points to use to draw the two faces for each lines egment, the following linesegment is offset by 4 f_indices = ( self.__index_pattern + numpy.arange(0, 4 * len(normals), 4, dtype=numpy.int32).reshape((-1, 1)) ).reshape((-1, 3)) f_colors = numpy.repeat(polygon.mapLineTypeToColor(line_types), 4, 0) builder.addFacesWithColor(f_points, f_indices, f_colors) return builder.build()
def calculateBoundingBoxMesh(self): aabb = self.getBoundingBox() if aabb: bounding_box_mesh = MeshBuilder() rtf = aabb.maximum lbb = aabb.minimum bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back self._bounding_box_mesh = bounding_box_mesh.build()
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator): parent_node = SceneNode() parent_node.addDecorator(GroupDecorator()) node = SceneNode() parent_node.addChild(node) mb = MeshBuilder() mb.addCube(10, 10, 10) node.setMeshData(mb.build()) convex_hull_decorator._getSettingProperty = MagicMock(return_value=0) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(parent_node) with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"): copied_decorator = copy.deepcopy(convex_hull_decorator) copied_decorator._getSettingProperty = MagicMock(return_value=0) node.addDecorator(copied_decorator) assert convex_hull_decorator._compute2DConvexHull() == Polygon([[-5.0,5.0], [5.0,5.0], [5.0,-5.0], [-5.0,-5.0]])
def setMeshDataFromPywimTriangles(self, face: pywim.geom.tri.Face, axis: pywim.geom.Vector = None): if len(face.triangles) == 0: return self.face = face self.axis = axis mb = MeshBuilder() for tri in self.face.triangles: mb.addFace(tri.v1, tri.v2, tri.v3) mb.calculateNormals() self.setMeshData(mb.build()) self._setupTools()
def createHullMesh(self, hull_points): # Input checking. if len(hull_points) < 3: return None mesh_builder = MeshBuilder() point_first = Vector(hull_points[0][0], self._mesh_height, hull_points[0][1]) point_previous = Vector(hull_points[1][0], self._mesh_height, hull_points[1][1]) for point in hull_points[ 2:]: # Add the faces in the order of a triangle fan. point_new = Vector(point[0], self._mesh_height, point[1]) mesh_builder.addFace(point_first, point_previous, point_new, color=self._color) point_previous = point_new # Prepare point_previous for the next triangle. return mesh_builder.build()
def test_setCenterPosition(self): node = SceneNode() child_node = SceneNode() node.addChild(child_node) child_node.setCenterPosition = MagicMock() builder = MeshBuilder() builder.addVertex(10, 20, 20) node.setMeshData(builder.build()) node.setCenterPosition(Vector(-10, 0, 0)) transformed_mesh = node.getMeshData() transformed_vertex = transformed_mesh.getVertices()[0] assert transformed_vertex[0] == 20 assert transformed_vertex[1] == 20 assert transformed_vertex[2] == 20 child_node.setCenterPosition.assert_called_once_with(Vector(-10, 0, 0))
def drawSelection(self): if self._tri is None: return ph = self._connector.propertyHandler if ph._selection_mode is SelectionMode.AnchorMode: self.setFace(ph._anchoredTris) else: self.setFace(ph._loadedTris) # Construct Edges using MeshBuilder Cubes mb = MeshBuilder() for tri in self._tri: mb.addFace(tri.v1, tri.v2, tri.v3, color=self._selected_color) if self._connector.propertyHandler._selection_mode == SelectionMode.LoadMode: self.paintArrow(self._tri, mb) # Add to Cura Scene self.setSolidMesh(mb.build())
def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None: super().__init__(parent) self.setCalculateBoundingBox(False) self._original_parent = parent # Color of the drawn convex hull if not Application.getInstance().getIsHeadLess(): theme = QtApplication.getInstance().getTheme() if theme: self._color = Color(*theme.getColor("convex_hull").getRgb()) else: self._color = Color(0, 0, 0) else: self._color = Color(0, 0, 0) # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 self._thickness = thickness # The node this mesh is "watching" self._node = node self._convex_hull_head_mesh = None self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() if hull_mesh_builder.addConvexPolygonExtrusion( self._hull.getPoints()[::-1], # bottom layer is reversed self._mesh_height-thickness, self._mesh_height, color=self._color): hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh)
def _createEraserMesh(self): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) mesh = MeshBuilder() mesh.addCube(10, 10, 10) node.setMeshData(mesh.build()) active_build_plate = Application.getInstance().getBuildPlateModel( ).activeBuildPlate node.addDecorator(SettingOverrideDecorator()) node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration( "getStack" ) #Don't try to get the active extruder since it may be None anyway. if not stack: node.addDecorator(SettingOverrideDecorator()) stack = node.callDecoration("getStack") settings = stack.getTop() if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")): definition = stack.getSettingDefinition("anti_overhang_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState( ) # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) scene = self._controller.getScene() op = AddSceneNodeOperation(node, scene.getRoot()) op.push() Application.getInstance().getController().getScene().sceneChanged.emit( node)
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator): parent_node = SceneNode() parent_node.addDecorator(GroupDecorator()) node = SceneNode() parent_node.addChild(node) mb = MeshBuilder() mb.addCube(10, 10, 10) node.setMeshData(mb.build()) convex_hull_decorator._getSettingProperty = MagicMock(return_value=0) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(parent_node) with patch( "cura.Settings.ExtruderManager.ExtruderManager.getInstance"): copied_decorator = copy.deepcopy(convex_hull_decorator) copied_decorator._getSettingProperty = MagicMock(return_value=0) node.addDecorator(copied_decorator) assert convex_hull_decorator._compute2DConvexHull() == Polygon( [[-5.0, 5.0], [5.0, 5.0], [5.0, -5.0], [-5.0, -5.0]])
def _createEraserMesh(self, parent: CuraSceneNode, position: Vector): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) mesh = MeshBuilder() mesh.addCube(10, 10, 10) node.setMeshData(mesh.build()) node.setPosition(position) active_build_plate = Application.getInstance().getMultiBuildPlateModel( ).activeBuildPlate node.addDecorator(SettingOverrideDecorator()) node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration( "getStack") # created by SettingOverrideDecorator settings = stack.getTop() definition = stack.getSettingDefinition("anti_overhang_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState( ) # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) root = self._controller.getScene().getRoot() op = GroupedOperation() # First add the node to the scene, so it gets the expected transform op.addOperation(AddSceneNodeOperation(node, root)) op.addOperation(SetParentOperation(node, parent)) op.push() Application.getInstance().getController().getScene().sceneChanged.emit( node)
def _createSupportMesh(self, parent: CuraSceneNode, position: Vector): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) mesh = MeshBuilder() mesh.addCube(5, 5, 5) node.setMeshData(mesh.build()) active_build_plate = Application.getInstance().getMultiBuildPlateModel( ).activeBuildPlate node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration( "getStack" ) # created by SettingOverrideDecorator that is automatically added to CuraSceneNode settings = stack.getTop() definition = stack.getSettingDefinition("support_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState( ) # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) op = GroupedOperation() # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent op.addOperation( AddSceneNodeOperation(node, self._controller.getScene().getRoot())) op.addOperation(SetParentOperation(node, parent)) op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) Application.getInstance().getController().getScene().sceneChanged.emit( node)
def createMeshOrJumps(self, make_mesh): builder = MeshBuilder() for polygon in self._polygons: if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType): continue if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType): continue poly_color = polygon.getColor() points = numpy.copy(polygon.data) if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType: points[:,1] -= 0.01 if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType: points[:,1] += 0.01 normals = polygon.getNormals() # Scale all by the line width of the polygon so we can easily offset. normals *= (polygon.lineWidth / 2) #TODO: Use numpy magic to perform the vertex creation to speed up things. for i in range(len(points)): start = points[i - 1] end = points[i] normal = normals[i - 1] point1 = Vector(data = start - normal) point2 = Vector(data = start + normal) point3 = Vector(data = end + normal) point4 = Vector(data = end - normal) builder.addQuad(point1, point2, point3, point4, color = poly_color) return builder.build()
def __init__(self, node, hull, thickness, parent=None): super().__init__(parent) self.setCalculateBoundingBox(False) self._shader = None self._original_parent = parent # Color of the drawn convex hull self._color = None # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 self._thickness = thickness # The node this mesh is "watching" self._node = node self._convex_hull_head_mesh = None self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() if hull_mesh_builder.addConvexPolygonExtrusion( self._hull.getPoints()[::-1], # bottom layer is reversed self._mesh_height - thickness, self._mesh_height, color=self._color): hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh)
def _getAxisMesh(self, node): mb = MeshBuilder() mb.addCube( width=self.axis_width, height=self.axis_height, depth=self.axis_width, color=self.YAxisColor, center = Vector(0, self.axis_height / 2, 0) ) mb.addCube( width=self.axis_width, height=self.axis_width, depth=self.axis_height, color=self.ZAxisColor, center=Vector(0, 0, self.axis_height / 2) ) mb.addCube( width=self.axis_height, height=self.axis_width, depth=self.axis_width, color=self.XAxisColor, center=Vector(self.axis_height / 2, 0, 0) ) return mb.build().getTransformed(node.getWorldTransformation())
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 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.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()) # 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._height / 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.VolumeOutlineColor) mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self.VolumeOutlineColor) 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.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() 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 read(self, file_name): result = SceneNode() # The base object of 3mf is a zipped archive. archive = zipfile.ZipFile(file_name, "r") try: root = ET.parse(archive.open("3D/3dmodel.model")) # There can be multiple objects, try to load all of them. objects = root.findall("./3mf:resources/3mf:object", self._namespaces) if len(objects) == 0: Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name) return None for entry in objects: mesh_builder = MeshBuilder() node = SceneNode() vertex_list = [] #for vertex in entry.mesh.vertices.vertex: for vertex in entry.findall(".//3mf:vertex", self._namespaces): vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) Job.yieldThread() triangles = entry.findall(".//3mf:triangle", self._namespaces) mesh_builder.reserveFaceCount(len(triangles)) #for triangle in object.mesh.triangles.triangle: for triangle in triangles: v1 = int(triangle.get("v1")) v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2], vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2], vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2]) Job.yieldThread() # Rotate the model; We use a different coordinate frame. rotation = Matrix() rotation.setByRotationAxis(-0.5 * math.pi, Vector(1,0,0)) #TODO: We currently do not check for normals and simply recalculate them. mesh_builder.calculateNormals() node.setMeshData(mesh_builder.build().getTransformed(rotation)) node.setSelectable(True) transformations = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(entry.get("id")), self._namespaces) transformation = transformations[0] if transformations else None if transformation is not None and transformation.get("transform"): splitted_transformation = transformation.get("transform").split() ## Transformation is saved as: ## M00 M01 M02 0.0 ## M10 M11 M12 0.0 ## M20 M21 M22 0.0 ## M30 M31 M32 1.0 ## We switch the row & cols as that is how everyone else uses matrices! temp_mat = Matrix() # Rotation & Scale temp_mat._data[0,0] = splitted_transformation[0] temp_mat._data[1,0] = splitted_transformation[1] temp_mat._data[2,0] = splitted_transformation[2] temp_mat._data[0,1] = splitted_transformation[3] temp_mat._data[1,1] = splitted_transformation[4] temp_mat._data[2,1] = splitted_transformation[5] temp_mat._data[0,2] = splitted_transformation[6] temp_mat._data[1,2] = splitted_transformation[7] temp_mat._data[2,2] = splitted_transformation[8] # Translation temp_mat._data[0,3] = splitted_transformation[9] temp_mat._data[1,3] = splitted_transformation[10] temp_mat._data[2,3] = splitted_transformation[11] node.setTransformation(temp_mat) result.addChild(node) Job.yieldThread() #If there is more then one object, group them. if len(objects) > 1: group_decorator = GroupDecorator() result.addDecorator(group_decorator) except Exception as e: Logger.log("e" ,"exception occured in 3mf reader: %s" , e) return result
def _createNodeFromObject(self, object, name=""): node = SceneNode() node.setName(name) mesh_builder = MeshBuilder() vertex_list = [] components = object.find(".//3mf:components", self._namespaces) if components: for component in components: id = component.get("objectid") new_object = self._root.find( "./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces) new_node = self._createNodeFromObject( new_object, self._base_name + "_" + str(id)) node.addChild(new_node) transform = component.get("transform") if transform is not None: new_node.setTransformation( self._createMatrixFromTransformationString(transform)) # for vertex in entry.mesh.vertices.vertex: for vertex in object.findall(".//3mf:vertex", self._namespaces): vertex_list.append( [vertex.get("x"), vertex.get("y"), vertex.get("z")]) Job.yieldThread() xml_settings = list(object.findall(".//cura:setting", self._namespaces)) # Add the setting override decorator, so we can add settings to this node. if xml_settings: node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: multi_extrusion = global_container_stack.getProperty( "machine_extruder_count", "value") > 1 # Ensure that all extruder data is reset if not multi_extrusion: default_stack_id = global_container_stack.getId() else: default_stack = ExtruderManager.getInstance( ).getExtruderStack(0) if default_stack: default_stack_id = default_stack.getId() else: default_stack_id = global_container_stack.getId() node.callDecoration("setActiveExtruder", default_stack_id) # Get the definition & set it definition = QualityManager.getInstance( ).getParentMachineDefinition( global_container_stack.getBottom()) node.callDecoration("getStack").getTop().setDefinition( definition) setting_container = node.callDecoration("getStack").getTop() for setting in xml_settings: setting_key = setting.get("key") setting_value = setting.text # Extruder_nr is a special case. if setting_key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(setting_key, "value", setting_value) if len(node.getChildren()) > 0: group_decorator = GroupDecorator() node.addDecorator(group_decorator) triangles = object.findall(".//3mf:triangle", self._namespaces) mesh_builder.reserveFaceCount(len(triangles)) for triangle in triangles: v1 = int(triangle.get("v1")) v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh_builder.addFaceByPoints( vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2], vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2], vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2]) Job.yieldThread() # TODO: We currently do not check for normals and simply recalculate them. mesh_builder.calculateNormals(fast=True) mesh_builder.setFileName(name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): node.setMeshData(mesh_data) node.setSelectable(True) return node
def buildMesh(self): mb = MeshBuilder() #SOLIDMESH mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = self._y_axis_color ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, -self._handle_position, 0), color = self._y_axis_color, axis = Vector.Unit_X, angle = 180 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = self._x_axis_color, axis = Vector.Unit_Z, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(-self._handle_position, 0, 0), color = self._x_axis_color, axis = Vector.Unit_Z, angle = -90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, -self._handle_position), color = self._z_axis_color, axis = Vector.Unit_X, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = self._z_axis_color, axis = Vector.Unit_X, angle = -90 ) self.setSolidMesh(mb.build()) #SELECTIONMESH mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = ToolHandle.YAxisSelectionColor ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, -self._handle_position, 0), color = ToolHandle.YAxisSelectionColor, axis = Vector.Unit_X, angle = 180 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = ToolHandle.XAxisSelectionColor, axis = Vector.Unit_Z, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(-self._handle_position, 0, 0), color = ToolHandle.XAxisSelectionColor, axis = Vector.Unit_Z, angle = -90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, -self._handle_position), color = ToolHandle.ZAxisSelectionColor, axis = Vector.Unit_X, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = ToolHandle.ZAxisSelectionColor, axis = Vector.Unit_X, angle = -90 ) self.setSelectionMesh(mb.build())
def read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) f = open(file_name, "rt") for line in f: parts = line.split() if len(parts) < 1: continue if parts[0] == "v": vertex_list.append([float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vn": normal_list.append([float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts)-2): data = [int(parts[1][0]), int(parts[idx+1][0]), int(parts[idx+2][0])] if len(parts[1]) > 2: data += [int(parts[1][2]), int(parts[idx+1][2]), int(parts[idx+2][2])] if parts[1][1] and parts[idx+1][1] and parts[idx+2][1]: data += [int(parts[1][1]), int(parts[idx+1][1]), int(parts[idx+2][1])] face_list.append(data) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) num_normals = len(normal_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 if len(face) > 3: ni = face[3] - 1 nj = face[4] - 1 nk = face[5] - 1 else: ni = -1 nj = -1 nk = -1 if len(face) > 6: ui = face[6] - 1 uj = face[7] - 1 uk = face[8] - 1 else: ui = -1 uj = -1 uk = -1 #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled) if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2],normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast = True) scene_node.setMeshData(mesh_builder.build()) return scene_node
def setNode(self, node): super().setNode(node) aabb = node.getBoundingBox() if not aabb.isValid(): return if not self._body: self._body = ode.Body(self._world) self._body.setMaxAngularSpeed(0) mass = ode.Mass() mass.setBox(5.0, Helpers.toODE(aabb.width), Helpers.toODE(aabb.height), Helpers.toODE(aabb.depth)) self._body.setMass(mass) if not self._geom: if node.getMeshData(): scale_matrix = Matrix() scale_matrix.setByScaleFactor(1.01) mesh = node.getMeshData().getTransformed(scale_matrix) self._trimesh = ode.TriMeshData() debug_builder = MeshBuilder() vertices = mesh.getVertices() indices = mesh.getConvexHull().simplices _fixWindingOrder(vertices, indices, debug_builder) self._trimesh.build(vertices / Helpers.ScaleFactor, indices) self._geom = ode.GeomTriMesh(self._trimesh, self._space) mb = MeshBuilder() for i in range(self._geom.getTriangleCount()): tri = self._geom.getTriangle(i) v0 = Helpers.fromODE(tri[0]) v1 = Helpers.fromODE(tri[1]) v2 = Helpers.fromODE(tri[2]) mb.addFace(v0=v0, v1=v1, v2=v2, color=Color(1.0, 0.0, 0.0, 0.5)) chn = SceneNode(node) chn.setMeshData(mb.build()) def _renderConvexHull(renderer): renderer.queueNode(chn, transparent=True) return True chn.render = _renderConvexHull n = SceneNode(node) n.setMeshData(debug_builder.build()) def _renderNormals(renderer): renderer.queueNode(n, mode=1, overlay=True) return True n.render = _renderNormals else: self._geom = ode.GeomBox(self._space, lengths=(Helpers.toODE(aabb.width), Helpers.toODE(aabb.height), Helpers.toODE(aabb.depth))) self._geom.setBody(self._body) self._body.setPosition(Helpers.toODE(node.getWorldPosition())) node.transformationChanged.connect(self._onTransformationChanged)
def beginRendering(self): # Convenience setup scene = self.getController().getScene() renderer = self.getRenderer() if not self._shader: self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) self._shader.setUniformValue("u_color", Color(32, 32, 32, 170)) for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): # For now we only render nodes that indicate that they need rendering. if node.getMeshData(): # Render origin of this node. renderer.queueNode(scene.getRoot(), mesh = self._getAxisMesh(node), transparent = True) # Render transparent MeshData renderer.queueNode(node, shader = self._shader, transparent = True) billboard_node = self._ensureNodeHasBillboard(node) # Update the displayed data on the billboard. data = self._matrixToHtml(node.getLocalTransformation()) billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": node.getParent().getName(), "has_mesh": "True"}) # Handle group nodes if node.callDecoration("isGroup"): # Render origin of this node. renderer.queueNode(scene.getRoot(), mesh=self._getAxisMesh(node), transparent = True) # Render bounding box of this node renderer.queueNode(scene.getRoot(), mesh=node.getBoundingBoxMesh(), mode=Renderer.RenderLines) billboard_node = self._ensureNodeHasBillboard(node) # Update the displayed data on the billboard. data = self._matrixToHtml(node.getLocalTransformation()) billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": node.getParent().getName(), "has_mesh": node.getMeshData() is not None}) # We sometimes have nodes that are not groups, but have children. Also draw them elif not node.getMeshData() and len(node.getChildren()) != 0: # Render origin of this node. renderer.queueNode(scene.getRoot(), mesh=self._getAxisMesh(node), transparent = True) billboard_node = self._ensureNodeHasBillboard(node) # Update the displayed data on the billboard. data = self._matrixToHtml(node.getLocalTransformation()) parent_name = node.getParent().getName() if node.getParent() else "" billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": parent_name, "has_mesh": "False"}) bounding_box = node.getBoundingBox() if bounding_box: mesh_builder = MeshBuilder() mesh_builder.addCube( width=bounding_box.width, height=bounding_box.height, depth=bounding_box.depth, center=bounding_box.center, color=Color(0.0, 0.0, 1.0, 1.0) ) mesh = mesh_builder.build() renderer.queueNode(scene.getRoot(), mesh=mesh, mode=Renderer.RenderLines)
def __init__(self, parent = None): super().__init__(parent) self._inner_radius = 40 self._outer_radius = 40.5 self._line_width = 0.5 self._active_inner_radius = 37 self._active_outer_radius = 44 self._active_line_width = 3 #SOLIDMESH mb = MeshBuilder() mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, color = ToolHandle.ZAxisColor ) mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, axis = Vector.Unit_X, angle = math.pi / 2, color = ToolHandle.YAxisColor ) mb.addDonut( inner_radius = self._inner_radius, outer_radius = self._outer_radius, width = self._line_width, axis = Vector.Unit_Y, angle = math.pi / 2, color = ToolHandle.XAxisColor ) self.setSolidMesh(mb.build()) #SELECTIONMESH mb = MeshBuilder() mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, color = ToolHandle.ZAxisColor ) mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, axis = Vector.Unit_X, angle = math.pi / 2, color = ToolHandle.YAxisColor ) mb.addDonut( inner_radius = self._active_inner_radius, outer_radius = self._active_outer_radius, width = self._active_line_width, axis = Vector.Unit_Y, angle = math.pi / 2, color = ToolHandle.XAxisColor ) self.setSelectionMesh(mb.build())
def buildMesh(self): mb = MeshBuilder() #SOLIDMESH -> LINES if self.YAxis in self._enabled_axis: mb.addCube(width=self._line_width, height=self._line_length, depth=self._line_width, center=Vector(0, self._handle_position / 2, 0), color=self._y_axis_color) if self.XAxis in self._enabled_axis: mb.addCube(width=self._line_length, height=self._line_width, depth=self._line_width, center=Vector(self._handle_position / 2, 0, 0), color=self._x_axis_color) if self.ZAxis in self._enabled_axis: mb.addCube(width=self._line_width, height=self._line_width, depth=self._line_length, center=Vector(0, 0, self._handle_position / 2), color=self._z_axis_color) #SOLIDMESH -> HANDLES if self.YAxis in self._enabled_axis: mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(0, self._handle_position, 0), color=self._y_axis_color) if self.XAxis in self._enabled_axis: mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(self._handle_position, 0, 0), color=self._x_axis_color, axis=Vector.Unit_Z, angle=90) if self.ZAxis in self._enabled_axis: mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(0, 0, self._handle_position), color=self._z_axis_color, axis=Vector.Unit_X, angle=-90) self.setSolidMesh(mb.build()) mb = MeshBuilder() #SELECTIONMESH -> LINES if self.YAxis in self._enabled_axis: mb.addCube(width=self._active_line_width, height=self._active_line_length, depth=self._active_line_width, center=Vector(0, self._active_handle_position / 2, 0), color=self._y_axis_color) if self.XAxis in self._enabled_axis: mb.addCube(width=self._active_line_length, height=self._active_line_width, depth=self._active_line_width, center=Vector(self._active_handle_position / 2, 0, 0), color=self._x_axis_color) if self.ZAxis in self._enabled_axis: mb.addCube(width=self._active_line_width, height=self._active_line_width, depth=self._active_line_length, center=Vector(0, 0, self._active_handle_position / 2), color=self._z_axis_color) #SELECTIONMESH -> HANDLES mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, 0, 0), color=ToolHandle.AllAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, self._active_handle_position, 0), color=ToolHandle.YAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(self._active_handle_position, 0, 0), color=ToolHandle.XAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, 0, self._active_handle_position), color=ToolHandle.ZAxisSelectionColor) self.setSelectionMesh(mb.build())
def __init__(self, parent = None): super().__init__(parent) self._handle_width = 8 self._handle_height = 14 self._handle_position = 20 mb = MeshBuilder() mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = ToolHandle.YAxisColor ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, -self._handle_position, 0), color = ToolHandle.YAxisColor, axis = Vector.Unit_X, angle = 180 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = ToolHandle.XAxisColor, axis = Vector.Unit_Z, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(-self._handle_position, 0, 0), color = ToolHandle.XAxisColor, axis = Vector.Unit_Z, angle = -90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, -self._handle_position), color = ToolHandle.ZAxisColor, axis = Vector.Unit_X, angle = 90 ) mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = ToolHandle.ZAxisColor, axis = Vector.Unit_X, angle = -90 ) mesh = mb.build() self.setSolidMesh(mesh) self.setSelectionMesh(mesh)
def _createNodeFromObject(self, object, name = ""): node = SceneNode() node.setName(name) mesh_builder = MeshBuilder() vertex_list = [] components = object.find(".//3mf:components", self._namespaces) if components: for component in components: id = component.get("objectid") new_object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces) new_node = self._createNodeFromObject(new_object, self._base_name + "_" + str(id)) node.addChild(new_node) transform = component.get("transform") if transform is not None: new_node.setTransformation(self._createMatrixFromTransformationString(transform)) # for vertex in entry.mesh.vertices.vertex: for vertex in object.findall(".//3mf:vertex", self._namespaces): vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) Job.yieldThread() xml_settings = list(object.findall(".//cura:setting", self._namespaces)) # Add the setting override decorator, so we can add settings to this node. if xml_settings: node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance().getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 # Ensure that all extruder data is reset if not multi_extrusion: default_stack_id = global_container_stack.getId() else: default_stack = ExtruderManager.getInstance().getExtruderStack(0) if default_stack: default_stack_id = default_stack.getId() else: default_stack_id = global_container_stack.getId() node.callDecoration("setActiveExtruder", default_stack_id) # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) node.callDecoration("getStack").getTop().setDefinition(definition) setting_container = node.callDecoration("getStack").getTop() for setting in xml_settings: setting_key = setting.get("key") setting_value = setting.text # Extruder_nr is a special case. if setting_key == "extruder_nr": extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value)) if extruder_stack: node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(setting_key,"value", setting_value) if len(node.getChildren()) > 0: group_decorator = GroupDecorator() node.addDecorator(group_decorator) triangles = object.findall(".//3mf:triangle", self._namespaces) mesh_builder.reserveFaceCount(len(triangles)) for triangle in triangles: v1 = int(triangle.get("v1")) v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2], vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2], vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2]) Job.yieldThread() # TODO: We currently do not check for normals and simply recalculate them. mesh_builder.calculateNormals() mesh_builder.setFileName(name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): node.setMeshData(mesh_data) node.setSelectable(True) return node
def buildMesh(self): #SOLIDMESH -> LINES mb = MeshBuilder() mb.addCube(width=self._line_width, height=self._line_length, depth=self._line_width, center=Vector(0, self._handle_position / 2, 0), color=self._y_axis_color) mb.addCube(width=self._line_length, height=self._line_width, depth=self._line_width, center=Vector(self._handle_position / 2, 0, 0), color=self._x_axis_color) mb.addCube(width=self._line_width, height=self._line_width, depth=self._line_length, center=Vector(0, 0, self._handle_position / 2), color=self._z_axis_color) #SOLIDMESH -> HANDLES mb.addCube(width=self._handle_width, height=self._handle_width, depth=self._handle_width, center=Vector(0, 0, 0), color=self._all_axis_color) mb.addCube(width=self._handle_width, height=self._handle_width, depth=self._handle_width, center=Vector(0, self._handle_position, 0), color=self._y_axis_color) mb.addCube(width=self._handle_width, height=self._handle_width, depth=self._handle_width, center=Vector(self._handle_position, 0, 0), color=self._x_axis_color) mb.addCube(width=self._handle_width, height=self._handle_width, depth=self._handle_width, center=Vector(0, 0, self._handle_position), color=self._z_axis_color) self.setSolidMesh(mb.build()) #SELECTIONMESH -> LINES mb = MeshBuilder() mb.addCube(width=self._active_line_width, height=self._active_line_length, depth=self._active_line_width, center=Vector(0, self._active_handle_position / 2, 0), color=ToolHandle.YAxisSelectionColor) mb.addCube(width=self._active_line_length, height=self._active_line_width, depth=self._active_line_width, center=Vector(self._active_handle_position / 2, 0, 0), color=ToolHandle.XAxisSelectionColor) mb.addCube(width=self._active_line_width, height=self._active_line_width, depth=self._active_line_length, center=Vector(0, 0, self._active_handle_position / 2), color=ToolHandle.ZAxisSelectionColor) #SELECTIONMESH -> HANDLES mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, 0, 0), color=ToolHandle.AllAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, self._active_handle_position, 0), color=ToolHandle.YAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(self._active_handle_position, 0, 0), color=ToolHandle.XAxisSelectionColor) mb.addCube(width=self._active_handle_width, height=self._active_handle_width, depth=self._active_handle_width, center=Vector(0, 0, self._active_handle_position), color=ToolHandle.ZAxisSelectionColor) self.setSelectionMesh(mb.build())
def __init__(self, parent = None): super().__init__(parent) self._line_width = 0.5 self._line_length= 40 self._handle_position = 40 self._handle_width = 4 self._active_line_width = 0.8 self._active_line_length = 40 self._active_handle_position = 40 self._active_handle_width = 15 #SOLIDMESH -> LINES mb = MeshBuilder() mb.addCube( width = self._line_width, height = self._line_length, depth = self._line_width, center = Vector(0, self._handle_position/2, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = self._line_length, height = self._line_width, depth = self._line_width, center = Vector(self._handle_position/2, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = self._line_width, height = self._line_width, depth = self._line_length, center = Vector(0, 0, self._handle_position/2), color = ToolHandle.ZAxisColor ) #SOLIDMESH -> HANDLES mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, 0, 0), color = ToolHandle.AllAxisColor ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = ToolHandle.ZAxisColor ) self.setSolidMesh(mb.build()) #SELECTIONMESH -> LINES mb = MeshBuilder() mb.addCube( width = self._active_line_width, height = self._active_line_length, depth = self._active_line_width, center = Vector(0, self._active_handle_position/2, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = self._active_line_length, height = self._active_line_width, depth = self._active_line_width, center = Vector(self._active_handle_position/2, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = self._active_line_width, height = self._active_line_width, depth = self._active_line_length, center = Vector(0, 0, self._active_handle_position/2), color = ToolHandle.ZAxisColor ) #SELECTIONMESH -> HANDLES mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, 0), color = ToolHandle.AllAxisColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, self._active_handle_position, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(self._active_handle_position, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, self._active_handle_position), color = ToolHandle.ZAxisColor ) self.setSelectionMesh(mb.build())
def read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) f = open(file_name, "rt") for line in f: parts = line.split() if len(parts) < 1: continue if parts[0] == "v": vertex_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vn": normal_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts) - 2): data = [ int(parts[1][0]), int(parts[idx + 1][0]), int(parts[idx + 2][0]) ] if len(parts[1]) > 2: data += [ int(parts[1][2]), int(parts[idx + 1][2]), int(parts[idx + 2][2]) ] if parts[1][1] and parts[idx + 1][1] and parts[idx + 2][1]: data += [ int(parts[1][1]), int(parts[idx + 1][1]), int(parts[idx + 2][1]) ] face_list.append(data) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) num_normals = len(normal_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 if len(face) > 3: ni = face[3] - 1 nj = face[4] - 1 nk = face[5] - 1 else: ni = -1 nj = -1 nk = -1 if len(face) > 6: ui = face[6] - 1 uj = face[7] - 1 uk = face[8] - 1 else: ui = -1 uj = -1 uk = -1 #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled) if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2], normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast=True) scene_node.setMeshData(mesh_builder.build()) return scene_node
def buildMesh(self): mb = MeshBuilder() #SOLIDMESH -> LINES if self.YAxis in self._enabled_axis: mb.addCube( width = self._line_width, height = self._line_length, depth = self._line_width, center = Vector(0, self._handle_position/2, 0), color = self._y_axis_color ) if self.XAxis in self._enabled_axis: mb.addCube( width = self._line_length, height = self._line_width, depth = self._line_width, center = Vector(self._handle_position/2, 0, 0), color = self._x_axis_color ) if self.ZAxis in self._enabled_axis: mb.addCube( width = self._line_width, height = self._line_width, depth = self._line_length, center = Vector(0, 0, self._handle_position/2), color = self._z_axis_color ) #SOLIDMESH -> HANDLES if self.YAxis in self._enabled_axis: mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = self._y_axis_color ) if self.XAxis in self._enabled_axis: mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = self._x_axis_color, axis = Vector.Unit_Z, angle = 90 ) if self.ZAxis in self._enabled_axis: mb.addPyramid( width = self._handle_width, height = self._handle_height, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = self._z_axis_color, axis = Vector.Unit_X, angle = -90 ) self.setSolidMesh(mb.build()) mb = MeshBuilder() #SELECTIONMESH -> LINES if self.YAxis in self._enabled_axis: mb.addCube( width = self._active_line_width, height = self._active_line_length, depth = self._active_line_width, center = Vector(0, self._active_handle_position/2, 0), color = self._y_axis_color ) if self.XAxis in self._enabled_axis: mb.addCube( width = self._active_line_length, height = self._active_line_width, depth = self._active_line_width, center = Vector(self._active_handle_position/2, 0, 0), color = self._x_axis_color ) if self.ZAxis in self._enabled_axis: mb.addCube( width = self._active_line_width, height = self._active_line_width, depth = self._active_line_length, center = Vector(0, 0, self._active_handle_position/2), color = self._z_axis_color ) #SELECTIONMESH -> HANDLES mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, 0), color = ToolHandle.AllAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, self._active_handle_position, 0), color = ToolHandle.YAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(self._active_handle_position, 0, 0), color = ToolHandle.XAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, self._active_handle_position), color = ToolHandle.ZAxisSelectionColor ) self.setSelectionMesh(mb.build())
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 if self._prime_tower_area: mb = MeshBuilder() color = Color(1.0, 0.0, 0.0, 0.5) points = self._prime_tower_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._prime_tower_area_mesh = mb.build() else: self._prime_tower_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 _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() img = QImage(file_name) if img.isNull(): Logger.log("e", "Image is corrupt.") return None width = max(img.width(), 2) height = max(img.height(), 2) aspect = height / width if img.width() < 2 or img.height() < 2: img = img.scaled(width, height, Qt.IgnoreAspectRatio) base_height = max(base_height, 0) peak_height = max(peak_height, -base_height) xz_size = max(xz_size, 1) scale_vector = Vector(xz_size, peak_height, xz_size) if width > height: scale_vector = scale_vector.set(z=scale_vector.z * aspect) elif height > width: scale_vector = scale_vector.set(x=scale_vector.x / aspect) if width > max_size or height > max_size: scale_factor = max_size / width if height > width: scale_factor = max_size / height width = int(max(round(width * scale_factor), 2)) height = int(max(round(height * scale_factor), 2)) img = img.scaled(width, height, Qt.IgnoreAspectRatio) width_minus_one = width - 1 height_minus_one = height - 1 Job.yieldThread() texel_width = 1.0 / (width_minus_one) * scale_vector.x texel_height = 1.0 / (height_minus_one) * scale_vector.z height_data = numpy.zeros((height, width), dtype=numpy.float32) for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) if use_transparency_model: height_data[y, x] = ( 0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) else: height_data[y, x] = ( 0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb) ) / 255 # fast computation ignoring gamma and degamma Job.yieldThread() if lighter_is_higher == use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge") height_data += copy[1:-1, 2:] height_data += copy[1:-1, :-2] height_data += copy[2:, 1:-1] height_data += copy[:-2, 1:-1] height_data += copy[2:, 2:] height_data += copy[:-2, 2:] height_data += copy[2:, :-2] height_data += copy[:-2, :-2] height_data /= 9 Job.yieldThread() if use_transparency_model: divisor = 1.0 / math.log( transmittance_1mm / 100.0 ) # log-base doesn't matter here. Precompute this value for faster computation of each pixel. min_luminance = (transmittance_1mm / 100.0)**(peak_height - base_height) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + ( 1.0 - min_luminance) * height_data[y, x] height_data[y, x] = base_height + divisor * math.log( mapped_luminance ) # use same base as a couple lines above this else: height_data *= scale_vector.y height_data += base_height if img.hasAlphaChannel(): for x in range(0, width): for y in range(0, height): height_data[y, x] *= qAlpha(img.pixel(x, y)) / 255.0 heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * ( height_minus_one * 2) + 2 mesh.reserveFaceCount(total_face_count) # initialize to texel space vertex offsets. # 6 is for 6 vertices for each texel quad. heightmap_vertices = numpy.zeros( (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32) heightmap_vertices = heightmap_vertices + numpy.array( [[[0, base_height, 0], [0, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, 0], [0, base_height, 0]]], dtype=numpy.float32) offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1] offsetsx = numpy.array(offsetsx, numpy.float32).reshape( -1, 1) * texel_width offsetsz = numpy.array(offsetsz, numpy.float32).reshape( -1, 1) * texel_height # offsets for each texel quad heightmap_vertex_offsets = numpy.concatenate([ offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz ], 1) heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape( -1, 6, 3) # apply height data to y values heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, : -1].reshape( -1) heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1) heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[ 1:, 1:].reshape(-1) heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1) heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3) mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3) mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices mesh._vertex_count = heightmap_vertices.size // 3 mesh._face_count = heightmap_indices.size // 3 geo_width = width_minus_one * texel_width geo_height = height_minus_one * texel_height # bottom mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0) # north and south walls for n in range(0, width_minus_one): x = n * texel_width nx = (n + 1) * texel_width hn0 = height_data[0, n] hn1 = height_data[0, n + 1] hs0 = height_data[height_minus_one, n] hs1 = height_data[height_minus_one, n + 1] mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height) # west and east walls for n in range(0, height_minus_one): y = n * texel_height ny = (n + 1) * texel_height hw0 = height_data[n, 0] hw1 = height_data[n + 1, 0] he0 = height_data[n, width_minus_one] he1 = height_data[n + 1, width_minus_one] mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) scene_node.setMeshData(mesh.build()) return scene_node
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher): scene_node = SceneNode() mesh = MeshBuilder() img = QImage(file_name) if img.isNull(): Logger.log("e", "Image is corrupt.") return None width = max(img.width(), 2) height = max(img.height(), 2) aspect = height / width if img.width() < 2 or img.height() < 2: img = img.scaled(width, height, Qt.IgnoreAspectRatio) base_height = max(base_height, 0) peak_height = max(peak_height, -base_height) xz_size = max(xz_size, 1) scale_vector = Vector(xz_size, peak_height, xz_size) if width > height: scale_vector = scale_vector.set(z=scale_vector.z * aspect) elif height > width: scale_vector = scale_vector.set(x=scale_vector.x / aspect) if width > max_size or height > max_size: scale_factor = max_size / width if height > width: scale_factor = max_size / height width = int(max(round(width * scale_factor), 2)) height = int(max(round(height * scale_factor), 2)) img = img.scaled(width, height, Qt.IgnoreAspectRatio) width_minus_one = width - 1 height_minus_one = height - 1 Job.yieldThread() texel_width = 1.0 / (width_minus_one) * scale_vector.x texel_height = 1.0 / (height_minus_one) * scale_vector.z height_data = numpy.zeros((height, width), dtype=numpy.float32) for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) height_data[y, x] = avg Job.yieldThread() if not lighter_is_higher: height_data = 1 - height_data for _ in range(0, blur_iterations): copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge") height_data += copy[1:-1, 2:] height_data += copy[1:-1, :-2] height_data += copy[2:, 1:-1] height_data += copy[:-2, 1:-1] height_data += copy[2:, 2:] height_data += copy[:-2, 2:] height_data += copy[2:, :-2] height_data += copy[:-2, :-2] height_data /= 9 Job.yieldThread() height_data *= scale_vector.y height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * ( height_minus_one * 2) + 2 mesh.reserveFaceCount(total_face_count) # initialize to texel space vertex offsets. # 6 is for 6 vertices for each texel quad. heightmap_vertices = numpy.zeros( (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32) heightmap_vertices = heightmap_vertices + numpy.array( [[[0, base_height, 0], [0, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, 0], [0, base_height, 0]]], dtype=numpy.float32) offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1] offsetsx = numpy.array(offsetsx, numpy.float32).reshape( -1, 1) * texel_width offsetsz = numpy.array(offsetsz, numpy.float32).reshape( -1, 1) * texel_height # offsets for each texel quad heightmap_vertex_offsets = numpy.concatenate([ offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz ], 1) heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape( -1, 6, 3) # apply height data to y values heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, : -1].reshape( -1) heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1) heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[ 1:, 1:].reshape(-1) heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1) heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3) mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3) mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices mesh._vertex_count = heightmap_vertices.size // 3 mesh._face_count = heightmap_indices.size // 3 geo_width = width_minus_one * texel_width geo_height = height_minus_one * texel_height # bottom mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0) # north and south walls for n in range(0, width_minus_one): x = n * texel_width nx = (n + 1) * texel_width hn0 = height_data[0, n] hn1 = height_data[0, n + 1] hs0 = height_data[height_minus_one, n] hs1 = height_data[height_minus_one, n + 1] mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height) # west and east walls for n in range(0, height_minus_one): y = n * texel_height ny = (n + 1) * texel_height hw0 = height_data[n, 0] hw1 = height_data[n + 1, 0] he0 = height_data[n, width_minus_one] he1 = height_data[n + 1, width_minus_one] mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) scene_node.setMeshData(mesh.build()) return scene_node
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert): scene_node = SceneNode() mesh = MeshBuilder() img = QImage(file_name) if img.isNull(): Logger.log("e", "Image is corrupt.") return None width = max(img.width(), 2) height = max(img.height(), 2) aspect = height / width if img.width() < 2 or img.height() < 2: img = img.scaled(width, height, Qt.IgnoreAspectRatio) base_height = max(base_height, 0) peak_height = max(peak_height, -base_height) xz_size = max(xz_size, 1) scale_vector = Vector(xz_size, peak_height, xz_size) if width > height: scale_vector = scale_vector.set(z=scale_vector.z * aspect) elif height > width: scale_vector = scale_vector.set(x=scale_vector.x / aspect) if width > max_size or height > max_size: scale_factor = max_size / width if height > width: scale_factor = max_size / height width = int(max(round(width * scale_factor), 2)) height = int(max(round(height * scale_factor), 2)) img = img.scaled(width, height, Qt.IgnoreAspectRatio) width_minus_one = width - 1 height_minus_one = height - 1 Job.yieldThread() texel_width = 1.0 / (width_minus_one) * scale_vector.x texel_height = 1.0 / (height_minus_one) * scale_vector.z height_data = numpy.zeros((height, width), dtype=numpy.float32) for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) height_data[y, x] = avg Job.yieldThread() if image_color_invert: height_data = 1 - height_data for _ in range(0, blur_iterations): copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge") height_data += copy[1:-1, 2:] height_data += copy[1:-1, :-2] height_data += copy[2:, 1:-1] height_data += copy[:-2, 1:-1] height_data += copy[2:, 2:] height_data += copy[:-2, 2:] height_data += copy[2:, :-2] height_data += copy[:-2, :-2] height_data /= 9 Job.yieldThread() height_data *= scale_vector.y height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2 mesh.reserveFaceCount(total_face_count) # initialize to texel space vertex offsets. # 6 is for 6 vertices for each texel quad. heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32) heightmap_vertices = heightmap_vertices + numpy.array([[ [0, base_height, 0], [0, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, 0], [0, base_height, 0] ]], dtype = numpy.float32) offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1] offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height # offsets for each texel quad heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1) heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3) # apply height data to y values heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1) heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1) heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1) heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1) heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3) mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3) mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices mesh._vertex_count = heightmap_vertices.size // 3 mesh._face_count = heightmap_indices.size // 3 geo_width = width_minus_one * texel_width geo_height = height_minus_one * texel_height # bottom mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0) # north and south walls for n in range(0, width_minus_one): x = n * texel_width nx = (n + 1) * texel_width hn0 = height_data[0, n] hn1 = height_data[0, n + 1] hs0 = height_data[height_minus_one, n] hs1 = height_data[height_minus_one, n + 1] mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height) # west and east walls for n in range(0, height_minus_one): y = n * texel_height ny = (n + 1) * texel_height hw0 = height_data[n, 0] hw1 = height_data[n + 1, 0] he0 = height_data[n, width_minus_one] he1 = height_data[n + 1, width_minus_one] mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) scene_node.setMeshData(mesh.build()) return scene_node
def buildMesh(self): #SOLIDMESH mb = MeshBuilder() mb.addDonut(inner_radius=self._inner_radius, outer_radius=self._outer_radius, width=self._line_width, color=self._z_axis_color) mb.addDonut(inner_radius=self._inner_radius, outer_radius=self._outer_radius, width=self._line_width, axis=Vector.Unit_X, angle=math.pi / 2, color=self._y_axis_color) mb.addDonut(inner_radius=self._inner_radius, outer_radius=self._outer_radius, width=self._line_width, axis=Vector.Unit_Y, angle=math.pi / 2, color=self._x_axis_color) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(0, self._handle_offset_a, -self._handle_offset_b), color=self._x_axis_color, axis=Vector.Unit_X, angle=90 + self._angle_offset) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(0, self._handle_offset_a, self._handle_offset_b), color=self._x_axis_color, axis=Vector.Unit_X, angle=-90 - self._angle_offset) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(self._handle_offset_b, 0, self._handle_offset_a), color=self._y_axis_color, axis=Vector.Unit_Z, angle=90 - self._angle_offset) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(-self._handle_offset_b, 0, self._handle_offset_a), color=self._y_axis_color, axis=Vector.Unit_Z, angle=-90 + self._angle_offset) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(self._handle_offset_a, self._handle_offset_b, 0), color=self._z_axis_color, axis=Vector.Unit_Z, angle=-self._angle_offset) mb.addPyramid(width=self._handle_width, height=self._handle_height, depth=self._handle_width, center=Vector(self._handle_offset_a, -self._handle_offset_b, 0), color=self._z_axis_color, axis=Vector.Unit_Z, angle=180 + self._angle_offset) self.setSolidMesh(mb.build()) #SELECTIONMESH mb = MeshBuilder() mb.addDonut(inner_radius=self._active_inner_radius, outer_radius=self._active_outer_radius, width=self._active_line_width, color=ToolHandle.ZAxisSelectionColor) mb.addDonut(inner_radius=self._active_inner_radius, outer_radius=self._active_outer_radius, width=self._active_line_width, axis=Vector.Unit_X, angle=math.pi / 2, color=ToolHandle.YAxisSelectionColor) mb.addDonut(inner_radius=self._active_inner_radius, outer_radius=self._active_outer_radius, width=self._active_line_width, axis=Vector.Unit_Y, angle=math.pi / 2, color=ToolHandle.XAxisSelectionColor) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(0, self._handle_offset_a, self._handle_offset_b), color=self._extra_widgets_color_map[ self.ExtraWidgets.XPositive90.value], axis=Vector.Unit_X, angle=-90 - self._angle_offset) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(0, self._handle_offset_a, -self._handle_offset_b), color=self._extra_widgets_color_map[ self.ExtraWidgets.XNegative90.value], axis=Vector.Unit_X, angle=90 + self._angle_offset) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(self._handle_offset_b, 0, self._handle_offset_a), color=self._extra_widgets_color_map[ self.ExtraWidgets.YPositive90.value], axis=Vector.Unit_Z, angle=90 - self._angle_offset) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(-self._handle_offset_b, 0, self._handle_offset_a), color=self._extra_widgets_color_map[ self.ExtraWidgets.YNegative90.value], axis=Vector.Unit_Z, angle=-90 + self._angle_offset) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(self._handle_offset_a, self._handle_offset_b, 0), color=self._extra_widgets_color_map[ self.ExtraWidgets.ZPositive90.value], axis=Vector.Unit_Z, angle=-self._angle_offset) mb.addPyramid(width=self._active_handle_width, height=self._active_handle_height, depth=self._active_handle_width, center=Vector(self._handle_offset_a, -self._handle_offset_b, 0), color=self._extra_widgets_color_map[ self.ExtraWidgets.ZNegative90.value], axis=Vector.Unit_Z, angle=180 + self._angle_offset) self.setSelectionMesh(mb.build())
def _convertSavitarNodeToUMNode( self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: node_name = savitar_node.getName() node_id = savitar_node.getId() if node_name == "": if file_name != "": node_name = os.path.basename(file_name) else: node_name = "Object {}".format(node_id) active_build_plate = CuraApplication.getInstance( ).getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.setName(node_name) um_node.setId(node_id) transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) if file_name: # The filename is used to give the user the option to reload the file if it is changed on disk # It is only set for the root node of the 3mf file mesh_builder.setFileName(file_name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition_id = ContainerTree.getInstance().machines[ global_container_stack.definition.getId( )].quality_definition um_node.callDecoration("getStack").getTop().setDefinition( definition_id) setting_container = um_node.callDecoration("getStack").getTop() for key in settings: setting_value = settings[key] # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None: """Convex hull node is a special type of scene node that is used to display an area, to indicate the location an object uses on the buildplate. This area (or area's in case of one at a time printing) is then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded to represent the raft as well. """ super().__init__(parent) self.setCalculateBoundingBox(False) self._original_parent = parent # Color of the drawn convex hull if not Application.getInstance().getIsHeadLess(): theme = QtApplication.getInstance().getTheme() if theme: self._color = Color(*theme.getColor("convex_hull").getRgb()) else: self._color = Color(0, 0, 0) else: self._color = Color(0, 0, 0) # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 self._thickness = thickness # The node this mesh is "watching" self._node = node # Area of the head + fans for display as a shadow on the buildplate self._convex_hull_head_mesh = None # type: Optional[MeshData] self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() if self._thickness == 0: if hull_mesh_builder.addConvexPolygon( self._hull.getPoints()[::], # bottom layer is reversed self._mesh_height, color=self._color): hull_mesh_builder.resetNormals() hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh) else: if hull_mesh_builder.addConvexPolygonExtrusion( self._hull.getPoints() [::-1], # bottom layer is reversed self._mesh_height - thickness, self._mesh_height, color=self._color): hull_mesh_builder.resetNormals() hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh)
def _constructSupport(self, buffer: QImage) -> None: depth_pass = PickingPass( buffer.width(), buffer.height() ) #Instead of using the picking pass to pick for us, we need to bulk-pick digits so do this in Numpy. depth_pass.render() depth_image = depth_pass.getOutput() camera = CuraApplication.getInstance().getController().getScene( ).getActiveCamera() #to_support = qimage2ndarray.raw_view(buffer) #to_support= _qimageview(_qt.QImage(buffer)) to_support = self._raw_view(buffer) #depth = qimage2ndarray.recarray_view(depth_image) depth = self._recarray_view(depth_image) depth.a = 0 #Discard alpha channel. depth = depth.view(dtype=_np.int32).astype( _np.float32 ) / 1000 #Conflate the R, G and B channels to one 24-bit (cast to 32) float. Divide by 1000 to get mm. support_positions_2d = _np.array( _np.where(_np.bitwise_and(to_support == 255, depth < 16777)) ) #All the 2D coordinates on the screen where we want support. The 16777 is for points that don't land on a model. support_depths = _np.take( depth, support_positions_2d[0, :] * depth.shape[1] + support_positions_2d[1, :]) #The depth at those pixels. support_positions_2d = support_positions_2d.transpose( ) #We want rows with pixels, not columns with pixels. if len(support_positions_2d) == 0: Logger.log( "i", "Support was not drawn on the surface of any objects. Not creating support." ) return support_positions_2d[:, [0, 1]] = support_positions_2d[:, [ 1, 0 ]] #Swap columns to get OpenGL's coordinate system. camera_viewport = _np.array( [camera.getViewportWidth(), camera.getViewportHeight()]) support_positions_2d = support_positions_2d * 2.0 / camera_viewport - 1.0 #Scale to view coordinates (range -1 to 1). inverted_projection = _np.linalg.inv( camera.getProjectionMatrix().getData()) transformation = camera.getWorldTransformation().getData() transformation[:, 1] = -transformation[:, 1] #Invert Z to get OpenGL's coordinate system. #For each pixel, get the near and far plane. near = _np.ndarray((support_positions_2d.shape[0], 4)) near.fill(1) near[0:support_positions_2d.shape[0], 0:support_positions_2d.shape[1]] = support_positions_2d near[:, 2].fill(-1) near = _np.dot(inverted_projection, near.transpose()) near = _np.dot(transformation, near) near = near[0:3] / near[3] far = _np.ndarray((support_positions_2d.shape[0], 4)) far.fill(1) far[0:support_positions_2d.shape[0], 0:support_positions_2d.shape[1]] = support_positions_2d far = _np.dot(inverted_projection, far.transpose()) far = _np.dot(transformation, far) far = far[0:3] / far[3] #Direction is from near plane pixel to far plane pixel, normalised. direction = near - far direction /= _np.linalg.norm(direction, axis=0) #Final position is in the direction of the pixel, moving with <depth> mm away from the camera position. support_positions_3d = ( support_depths - 1 ) * direction #We want the support to appear just before the surface, not behind the surface, so - 1. support_positions_3d = support_positions_3d.transpose() camera_position_data = camera.getPosition().getData() support_positions_3d = support_positions_3d + camera_position_data #Create the vertices for the 3D mesh. #This mesh consists of a diamond-shape for each position that we traced. n = support_positions_3d.shape[0] Logger.log( "i", "Adding support in {num_pixels} locations.".format(num_pixels=n)) vertices = support_positions_3d.copy().astype(_np.float32) vertices = _np.resize(vertices, (n * 6, support_positions_3d.shape[1] )) #Resize will repeat all coordinates 6 times. #For each position, create a diamond shape around the position with 6 vertices. vertices[ n * 0:n * 1, 0] -= support_depths * 0.001 * self.globule_size #First corner (-x, +y). vertices[n * 0:n * 1, 2] += support_depths * 0.001 * self.globule_size vertices[ n * 1:n * 2, 0] += support_depths * 0.001 * self.globule_size #Second corner (+x, +y). vertices[n * 1:n * 2, 2] += support_depths * 0.001 * self.globule_size vertices[ n * 2:n * 3, 0] -= support_depths * 0.001 * self.globule_size #Third corner (-x, -y). vertices[n * 2:n * 3, 2] -= support_depths * 0.001 * self.globule_size vertices[ n * 3:n * 4, 0] += support_depths * 0.001 * self.globule_size #Fourth corner (+x, -y) vertices[n * 3:n * 4, 2] -= support_depths * 0.001 * self.globule_size vertices[n * 4:n * 5, 1] += support_depths * 0.001 * self.globule_size #Top side. vertices[ n * 5:n * 6, 1] -= support_depths * 0.001 * self.globule_size #Bottom side. #Create the faces of the diamond. indices = _np.arange(n, dtype=_np.int32) indices = _np.kron(indices, _np.ones( (3, 1))).astype(_np.int32).transpose() indices = _np.resize( indices, (n * 8, 3) ) #Creates 8 triangles using 3 times the same vertex, for each position: [[0, 0, 0], [1, 1, 1], ... , [0, 0, 0], [1, 1, 1], ... ] #indices[n * 0: n * 1, 0] += n * 0 #First corner. indices[n * 0:n * 1, 1] += n * 1 #Second corner. indices[n * 0:n * 1, 2] += n * 4 #Top side. indices[n * 1:n * 2, 0] += n * 1 #Second corner. indices[n * 1:n * 2, 1] += n * 3 #Fourth corner. indices[n * 1:n * 2, 2] += n * 4 #Top side. indices[n * 2:n * 3, 0] += n * 3 #Fourth corner. indices[n * 2:n * 3, 1] += n * 2 #Third corner. indices[n * 2:n * 3, 2] += n * 4 #Top side. indices[n * 3:n * 4, 0] += n * 2 #Third corner. #indices[n * 3: n * 4, 1] += n * 0 #First corner. indices[n * 3:n * 4, 2] += n * 4 #Top side. indices[n * 4:n * 5, 0] += n * 1 #Second corner. #indices[n * 4: n * 5, 1] += n * 0 #First corner. indices[n * 4:n * 5, 2] += n * 5 #Bottom side. indices[n * 5:n * 6, 0] += n * 3 #Fourth corner. indices[n * 5:n * 6, 1] += n * 1 #Second corner. indices[n * 5:n * 6, 2] += n * 5 #Bottom side. indices[n * 6:n * 7, 0] += n * 2 #Third corner. indices[n * 6:n * 7, 1] += n * 3 #Fourth corner. indices[n * 6:n * 7, 2] += n * 5 #Bottom side. #indices[n * 7: n * 8, 0] += n * 0 #First corner. indices[n * 7:n * 8, 1] += n * 2 #Third corner. indices[n * 7:n * 8, 2] += n * 5 #Bottom side. builder = MeshBuilder() builder.addVertices(vertices) builder.addIndices(indices) #Create the scene node. scene = CuraApplication.getInstance().getController().getScene() new_node = CuraSceneNode(parent=scene.getRoot(), name="BrushSupport") new_node.setSelectable(False) new_node.setMeshData(builder.build()) new_node.addDecorator( BuildPlateDecorator(CuraApplication.getInstance(). getMultiBuildPlateModel().activeBuildPlate)) new_node.addDecorator(SliceableObjectDecorator()) operation = GroupedOperation() #Figure out which mesh this piece of support belongs to. #TODO: You can draw support in one stroke over multiple meshes. The support would belong to an arbitrary one of these. selection_pass = CuraApplication.getInstance().getRenderer( ).getRenderPass("selection") parent_id = selection_pass.getIdAtPosition( support_positions_2d[0][0], support_positions_2d[0] [1]) #Find the selection under the first support pixel. parent_node = scene.getRoot() if not parent_id: Logger.log("d", "Can't link custom support to any scene node.") else: for node in BreadthFirstIterator(scene.getRoot()): if id(node) == parent_id: parent_node = node break #Add the appropriate per-object settings. stack = new_node.callDecoration( "getStack" ) #Created by SettingOverrideDecorator that is automatically added to CuraSceneNode. settings = stack.getTop() support_mesh_instance = SettingInstance( stack.getSettingDefinition("support_mesh"), settings) support_mesh_instance.setProperty("value", True) support_mesh_instance.resetState() settings.addInstance(support_mesh_instance) drop_down_instance = SettingInstance( stack.getSettingDefinition("support_mesh_drop_down"), settings) drop_down_instance.setProperty("value", True) drop_down_instance.resetState() settings.addInstance(drop_down_instance) #Add the scene node to the scene (and allow for undo). operation.addOperation( AddSceneNodeOperation(new_node, scene.getRoot()) ) #Set the parent to root initially, then change the parent, so that we don't have to alter the transformation. operation.addOperation(SetParentOperation(new_node, parent_node)) operation.push() scene.sceneChanged.emit(new_node)
def _convertSavitarNodeToUMNode(self, savitar_node): um_node = SceneNode() transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: um_node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition = QualityManager.getInstance( ).getParentMachineDefinition( global_container_stack.getBottom()) um_node.callDecoration("getStack").getTop().setDefinition( definition.getId()) setting_container = um_node.callDecoration("getStack").getTop() for key in settings: setting_value = settings[key] # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def updateSceneFromOptimizationResult( self, analysis: pywim.smartslice.result.Analysis): type_map = { 'int': int, 'float': float, 'str': str, 'enum': str, 'bool': bool } our_only_node = getPrintableNodes()[0] active_extruder = getNodeActiveExtruder(our_only_node) # TODO - Move this into a common class or function to apply an am.Config to GlobalStack/ExtruderStack if analysis.print_config.infill: infill_density = analysis.print_config.infill.density infill_pattern = analysis.print_config.infill.pattern if infill_pattern is None or infill_pattern == pywim.am.InfillType.unknown: infill_pattern = pywim.am.InfillType.grid infill_pattern_name = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[ infill_pattern] extruder_dict = { "wall_line_count": analysis.print_config.walls, "top_layers": analysis.print_config.top_layers, "bottom_layers": analysis.print_config.bottom_layers, "infill_sparse_density": analysis.print_config.infill.density, "infill_pattern": infill_pattern_name } Logger.log("d", "Optimized extruder settings: {}".format(extruder_dict)) for key, value in extruder_dict.items(): if value is not None: property_type = type_map.get( active_extruder.getProperty(key, "type")) if property_type: active_extruder.setProperty(key, "value", property_type(value), set_from_cache=True) active_extruder.setProperty(key, "state", InstanceState.User, set_from_cache=True) Application.getInstance().getMachineManager( ).forceUpdateAllSettings() self.optimizationResultAppliedToScene.emit() # Remove any modifier meshes which are present from a previous result mod_meshes = getModifierMeshes() if len(mod_meshes) > 0: for node in mod_meshes: node.addDecorator(SmartSliceRemovedDecorator()) our_only_node.removeChild(node) Application.getInstance().getController().getScene( ).sceneChanged.emit(node) # Add in the new modifier meshes for modifier_mesh in analysis.modifier_meshes: # Building the scene node modifier_mesh_node = CuraSceneNode() modifier_mesh_node.setName("SmartSliceMeshModifier") modifier_mesh_node.setSelectable(True) modifier_mesh_node.setCalculateBoundingBox(True) # Use the data from the SmartSlice engine to translate / rotate / scale the mod mesh modifier_mesh_node.setTransformation( Matrix(modifier_mesh.transform)) # Building the mesh # # Preparing the data from pywim for MeshBuilder modifier_mesh_vertices = [[v.x, v.y, v.z] for v in modifier_mesh.vertices] modifier_mesh_indices = [[triangle.v1, triangle.v2, triangle.v3] for triangle in modifier_mesh.triangles] # Doing the actual build modifier_mesh_data = MeshBuilder() modifier_mesh_data.setVertices( numpy.asarray(modifier_mesh_vertices, dtype=numpy.float32)) modifier_mesh_data.setIndices( numpy.asarray(modifier_mesh_indices, dtype=numpy.int32)) modifier_mesh_data.calculateNormals() modifier_mesh_node.setMeshData(modifier_mesh_data.build()) modifier_mesh_node.calculateBoundingBoxMesh() active_build_plate = Application.getInstance( ).getMultiBuildPlateModel().activeBuildPlate modifier_mesh_node.addDecorator( BuildPlateDecorator(active_build_plate)) modifier_mesh_node.addDecorator(SliceableObjectDecorator()) modifier_mesh_node.addDecorator(SmartSliceAddedDecorator()) bottom = modifier_mesh_node.getBoundingBox().bottom z_offset_decorator = ZOffsetDecorator() z_offset_decorator.setZOffset(bottom) modifier_mesh_node.addDecorator(z_offset_decorator) stack = modifier_mesh_node.callDecoration("getStack") settings = stack.getTop() modifier_mesh_node_infill_pattern = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[ modifier_mesh.print_config.infill.pattern] definition_dict = { "infill_mesh": True, "infill_pattern": modifier_mesh_node_infill_pattern, "infill_sparse_density": modifier_mesh.print_config.infill.density, "wall_line_count": modifier_mesh.print_config.walls, "top_layers": modifier_mesh.print_config.top_layers, "bottom_layers": modifier_mesh.print_config.bottom_layers, } Logger.log( "d", "Optimized modifier mesh settings: {}".format(definition_dict)) for key, value in definition_dict.items(): if value is not None: definition = stack.getSettingDefinition(key) property_type = type_map.get(stack.getProperty( key, "type")) if property_type: new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", property_type(value)) new_instance.resetState( ) # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) our_only_node.addChild(modifier_mesh_node) # emit changes and connect error tracker Application.getInstance().getController().getScene( ).sceneChanged.emit(modifier_mesh_node)
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 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 buildMesh(self): #SOLIDMESH -> LINES mb = MeshBuilder() mb.addCube( width = self._line_width, height = self._line_length, depth = self._line_width, center = Vector(0, self._handle_position/2, 0), color = self._y_axis_color ) mb.addCube( width = self._line_length, height = self._line_width, depth = self._line_width, center = Vector(self._handle_position/2, 0, 0), color = self._x_axis_color ) mb.addCube( width = self._line_width, height = self._line_width, depth = self._line_length, center = Vector(0, 0, self._handle_position/2), color = self._z_axis_color ) #SOLIDMESH -> HANDLES mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, 0, 0), color = self._all_axis_color ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, self._handle_position, 0), color = self._y_axis_color ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(self._handle_position, 0, 0), color = self._x_axis_color ) mb.addCube( width = self._handle_width, height = self._handle_width, depth = self._handle_width, center = Vector(0, 0, self._handle_position), color = self._z_axis_color ) self.setSolidMesh(mb.build()) #SELECTIONMESH -> LINES mb = MeshBuilder() mb.addCube( width = self._active_line_width, height = self._active_line_length, depth = self._active_line_width, center = Vector(0, self._active_handle_position/2, 0), color = ToolHandle.YAxisSelectionColor ) mb.addCube( width = self._active_line_length, height = self._active_line_width, depth = self._active_line_width, center = Vector(self._active_handle_position/2, 0, 0), color = ToolHandle.XAxisSelectionColor ) mb.addCube( width = self._active_line_width, height = self._active_line_width, depth = self._active_line_length, center = Vector(0, 0, self._active_handle_position/2), color = ToolHandle.ZAxisSelectionColor ) #SELECTIONMESH -> HANDLES mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, 0), color = ToolHandle.AllAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, self._active_handle_position, 0), color = ToolHandle.YAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(self._active_handle_position, 0, 0), color = ToolHandle.XAxisSelectionColor ) mb.addCube( width = self._active_handle_width, height = self._active_handle_width, depth = self._active_handle_width, center = Vector(0, 0, self._active_handle_position), color = ToolHandle.ZAxisSelectionColor ) self.setSelectionMesh(mb.build())
def _convertSavitarNodeToUMNode( self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. :returns: Scene node. """ try: node_name = savitar_node.getName() node_id = savitar_node.getId() except AttributeError: Logger.log( "e", "Outdated version of libSavitar detected! Please update to the newest version!" ) node_name = "" node_id = "" if node_name == "": if file_name != "": node_name = os.path.basename(file_name) else: node_name = "Object {}".format(node_id) active_build_plate = CuraApplication.getInstance( ).getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) try: um_node.addDecorator(ConvexHullDecorator()) except: pass um_node.setName(node_name) um_node.setId(node_id) transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) if file_name: # The filename is used to give the user the option to reload the file if it is changed on disk # It is only set for the root node of the 3mf file mesh_builder.setFileName(file_name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition_id = ContainerTree.getInstance().machines[ global_container_stack.definition.getId( )].quality_definition um_node.callDecoration("getStack").getTop().setDefinition( definition_id) setting_container = um_node.callDecoration("getStack").getTop() known_setting_keys = um_node.callDecoration( "getStack").getAllKeys() for key in settings: setting_value = settings[key].value # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue if key in known_setting_keys: setting_container.setProperty(key, "value", setting_value) else: um_node.metadata[key] = settings[key] if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None: if len(um_node.getAllChildren()) == 1: # We don't want groups of one, so move the node up one "level" child_node = um_node.getChildren()[0] parent_transformation = um_node.getLocalTransformation() child_transformation = child_node.getLocalTransformation() child_node.setTransformation( parent_transformation.multiply(child_transformation)) um_node = cast(CuraSceneNode, um_node.getChildren()[0]) else: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def _rebuild(self): if not self._build_volume._width or not self._build_volume._height or not self._build_volume._depth: return if not self._build_volume._engine_ready: return if not self._build_volume._volume_outline_color: theme = Application.getInstance().getTheme() self._build_volume._volume_outline_color = Color( *theme.getColor("volume_outline").getRgb()) self._build_volume._x_axis_color = Color( *theme.getColor("x_axis").getRgb()) self._build_volume._y_axis_color = Color( *theme.getColor("y_axis").getRgb()) self._build_volume._z_axis_color = Color( *theme.getColor("z_axis").getRgb()) self._build_volume._disallowed_area_color = Color( *theme.getColor("disallowed_area").getRgb()) self._build_volume._error_area_color = Color( *theme.getColor("error_area").getRgb()) ### START PATCH # Get a dict from the machine metadata optionally overriding the build volume # Note that CuraEngine is blissfully unaware of this; it is just what the user is shown in Cura limit_buildvolume = self._build_volume._global_container_stack.getMetaDataEntry( "limit_buildvolume", {}) if not isinstance(limit_buildvolume, dict): limit_buildvolume = {} min_w = limit_buildvolume.get("width", {}).get("minimum", -self._build_volume._width / 2) max_w = limit_buildvolume.get("width", {}).get("maximum", self._build_volume._width / 2) min_h = limit_buildvolume.get("height", {}).get("minimum", 0.0) max_h = limit_buildvolume.get("height", {}).get("maximum", self._build_volume._height) min_d = limit_buildvolume.get("depth", {}).get("minimum", -self._build_volume._depth / 2) max_d = limit_buildvolume.get("depth", {}).get("maximum", self._build_volume._depth / 2) ### END PATCH z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting if self._build_volume._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._build_volume._volume_outline_color) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color=self._build_volume._volume_outline_color) mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color=self._build_volume._volume_outline_color) self._build_volume.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._build_volume._grid_mesh = mb.build() else: # Bottom and top 'ellipse' of the build volume aspect = 1.0 scale_matrix = Matrix() if self._build_volume._width != 0: # Scale circular meshes by aspect ratio if width != height aspect = self._build_volume._depth / self._build_volume._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._build_volume._volume_outline_color) mb.addArc(max_w, Vector.Unit_Y, center=(0, max_h, 0), color=self._build_volume._volume_outline_color) self._build_volume.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._build_volume._grid_mesh = mb.build().getTransformed( scale_matrix) # Indication of the machine origin if self._build_volume._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._build_volume._origin_line_length, height=self._build_volume._origin_line_width, depth=self._build_volume._origin_line_width, center=origin + Vector(self._build_volume._origin_line_length / 2, 0, 0), color=self._build_volume._x_axis_color) mb.addCube(width=self._build_volume._origin_line_width, height=self._build_volume._origin_line_length, depth=self._build_volume._origin_line_width, center=origin + Vector(0, self._build_volume._origin_line_length / 2, 0), color=self._build_volume._y_axis_color) mb.addCube(width=self._build_volume._origin_line_width, height=self._build_volume._origin_line_width, depth=self._build_volume._origin_line_length, center=origin - Vector(0, 0, self._build_volume._origin_line_length / 2), color=self._build_volume._z_axis_color) self._build_volume._origin_mesh = mb.build() disallowed_area_height = 0.1 disallowed_area_size = 0 if self._build_volume._disallowed_areas: mb = MeshBuilder() color = self._build_volume._disallowed_area_color for polygon in self._build_volume._disallowed_areas: points = polygon.getPoints() if len(points) == 0: continue first = Vector( self._build_volume._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._build_volume._clamp(points[0][1], min_d, max_d)) previous_point = Vector( self._build_volume._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._build_volume._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector( self._build_volume._clamp(point[0], min_w, max_w), disallowed_area_height, self._build_volume._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._build_volume._disallowed_area_mesh = mb.build() else: self._build_volume._disallowed_area_mesh = None if self._build_volume._error_areas: mb = MeshBuilder() for error_area in self._build_volume._error_areas: color = self._build_volume._error_area_color points = error_area.getPoints() first = Vector( self._build_volume._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._build_volume._clamp(points[0][1], min_d, max_d)) previous_point = Vector( self._build_volume._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._build_volume._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector( self._build_volume._clamp(point[0], min_w, max_w), disallowed_area_height, self._build_volume._clamp(point[1], min_d, max_d)) mb.addFace(first, previous_point, new_point, color=color) previous_point = new_point self._build_volume._error_mesh = mb.build() else: self._build_volume._error_mesh = None self._build_volume._volume_aabb = AxisAlignedBox( minimum=Vector(min_w, min_h - 1.0, min_d), maximum=Vector( max_w, max_h - self._build_volume._raft_thickness - self._build_volume._extra_z_clearance, max_d)) bed_adhesion_size = self._build_volume.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._build_volume._raft_thickness - self._build_volume._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)) Application.getInstance().getController().getScene( )._maximum_bounds = scale_to_max_bounds self._build_volume.updateNodeBoundaryCheck()
def _convertSavitarNodeToUMNode(self, savitar_node): self._object_count += 1 node_name = "Object %s" % self._object_count active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.setName(node_name) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: global_container_stack = Application.getInstance().getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack(0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition) um_node.callDecoration("getStack").getTop().setDefinition(definition_id) setting_container = um_node.callDecoration("getStack").getTop() for key in settings: setting_value = settings[key] # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def _read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) previous_line_parts = [] f = open(file_name, "rt", encoding="utf-8") for line in f: parts = previous_line_parts + line.split() previous_line_parts = [] if len(parts) < 1: continue if parts[-1] == "\\": del parts[-1] previous_line_parts = parts continue if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts) - 2): data = self._toAbsoluteIndex(len(vertex_list), [ int(parts[1][0]), int(parts[idx + 1][0]), int(parts[idx + 2][0]) ]) if len(parts[1]) > 1 and parts[1][1] and parts[ idx + 1][1] and parts[idx + 2][1]: data += self._toAbsoluteIndex( len(normal_list), [ int(parts[1][1]), int(parts[idx + 1][1]), int(parts[idx + 2][1]) ]) else: data += [0, 0, 0] if len(parts[1]) > 2: data += self._toAbsoluteIndex( len(uv_list), [ int(parts[1][2]), int(parts[idx + 1][2]), int(parts[idx + 2][2]) ]) else: data += [0, 0, 0] face_list.append(data) elif parts[0] == "v": vertex_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) elif parts[0] == "vn": normal_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) elif parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 ui = face[3] - 1 uj = face[4] - 1 uk = face[5] - 1 ni = face[6] - 1 nj = face[7] - 1 nk = face[8] - 1 if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2], normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1 and len(uv_list) > ui: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1 and len(uv_list) > uj: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1 and len(uv_list) > uk: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast=True) # make sure that the mesh data is not empty if mesh_builder.getVertexCount() == 0: Logger.log("d", "File did not contain valid data, unable to read.") return None # We didn't load anything. scene_node.setMeshData(mesh_builder.build()) return scene_node