def _setMeshAttributes(self, mesh: MeshData) -> None: self._shader.enableAttribute("a_vertex", "vector3f", 0) vertex_count = mesh.getVertexCount() offset = vertex_count * 3 * 4 if mesh.hasNormals(): self._shader.enableAttribute("a_normal", "vector3f", offset) offset += vertex_count * 3 * 4 if mesh.hasColors(): self._shader.enableAttribute("a_color", "vector4f", offset) offset += vertex_count * 4 * 4 if mesh.hasUVCoordinates(): self._shader.enableAttribute("a_uvs", "vector2f", offset) offset += vertex_count * 2 * 4 for attribute_name in mesh.attributeNames(): attribute = mesh.getAttribute(attribute_name) self._shader.enableAttribute(attribute["opengl_name"], attribute["opengl_type"], offset) if attribute["opengl_type"] == "vector2f": offset += mesh.getVertexCount() * 2 * 4 elif attribute["opengl_type"] == "vector4f": offset += mesh.getVertexCount() * 4 * 4 elif attribute["opengl_type"] == "int": offset += mesh.getVertexCount() * 4 elif attribute["opengl_type"] == "float": offset += mesh.getVertexCount() * 4 else: Logger.log( "e", "Attribute with name [%s] uses non implemented type [%s]." % (attribute["opengl_name"], attribute["opengl_type"])) self._shader.disableAttribute(attribute["opengl_name"])
def test_counts(): empty_mesh = MeshData() assert empty_mesh.getFaceCount() == 0 assert empty_mesh.getVertexCount() == 0 filled_mesh = MeshData(indices = numpy.zeros((5, 3), dtype=numpy.float32), vertices = numpy.zeros((12, 3), dtype=numpy.float32)) assert filled_mesh.getFaceCount() == 5 assert filled_mesh.getVertexCount() == 12
def test_counts(): empty_mesh = MeshData() assert empty_mesh.getFaceCount() == 0 assert empty_mesh.getVertexCount() == 0 filled_mesh = MeshData(indices=numpy.zeros((5, 3), dtype=numpy.float32), vertices=numpy.zeros((12, 3), dtype=numpy.float32)) assert filled_mesh.getFaceCount() == 5 assert filled_mesh.getVertexCount() == 12
def makeInteractiveMesh(mesh_data: MeshData) -> 'pywim.geom.tri.Mesh': import pywim int_mesh = pywim.geom.tri.Mesh() verts = mesh_data.getVertices() for i in range(mesh_data.getVertexCount()): int_mesh.add_vertex(i, verts[i][0], verts[i][1], verts[i][2]) faces = mesh_data.getIndices() if faces is not None: for i in range(mesh_data.getFaceCount()): v1 = int_mesh.vertices[faces[i][0]] v2 = int_mesh.vertices[faces[i][1]] v3 = int_mesh.vertices[faces[i][2]] int_mesh.add_triangle(i, v1, v2, v3) else: for i in range(0, len(int_mesh.vertices), 3): v1 = int_mesh.vertices[i] v2 = int_mesh.vertices[i + 1] v3 = int_mesh.vertices[i + 2] int_mesh.add_triangle(i // 3, v1, v2, v3) # Cura keeps around degenerate triangles, so we need to as well # so we don't end up with a mismatch in triangle ids int_mesh.analyze_mesh(remove_degenerate_triangles=False) return int_mesh
def _toTriMesh(self, mesh_data: MeshData) -> trimesh.base.Trimesh: indices = mesh_data.getIndices() if indices is None: # some file formats (eg 3mf) don't supply indices, but have unique vertices per face indices = numpy.arange(mesh_data.getVertexCount()).reshape(-1, 3) return trimesh.base.Trimesh(vertices=mesh_data.getVertices(), faces=indices, vertex_normals=mesh_data.getNormals())
def createHullMesh(self, hull_points): mesh = MeshData() if len(hull_points) > 3: center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh.addVertex(center[0], -0.1, center[1]) else: return None for point in hull_points: mesh.addVertex(point[0], -0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) return mesh
def read(self, file_name, storage_device): mesh = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: mesh = MeshData() f = storage_device.openFile(file_name, "rb") if not self._loadBinary(mesh, f): storage_device.closeFile(f) f = storage_device.openFile(file_name, "rt") try: self._loadAscii(mesh, f) except UnicodeDecodeError: pass storage_device.closeFile(f) storage_device.closeFile(f) mesh.calculateNormals() Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount()) return mesh
def read(self, file_name): mesh = None scene_node = None mesh = MeshData() scene_node = SceneNode() f = open(file_name, "rb") if not self._loadBinary(mesh, f): f.close() f = open(file_name, "rt") try: self._loadAscii(mesh, f) except UnicodeDecodeError: pass f.close() f.close() time.sleep(0.1) #Yield somewhat to ensure the GUI has time to update a bit. mesh.calculateNormals(fast = True) Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount()) scene_node.setMeshData(mesh) return scene_node
def __init__(self, node, hull, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._material = None self._original_parent = parent self._inherit_orientation = False self._inherit_scale = False self._node = node self._node.transformationChanged.connect(self._onNodePositionChanged) self._node.parentChanged.connect(self._onNodeParentChanged) #self._onNodePositionChanged(self._node) self._hull = hull hull_points = self._hull.getPoints() mesh = MeshData() if len(hull_points) > 3: center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh.addVertex(center[0], 0.1, center[1]) else: #Hull has not enough points return for point in hull_points: mesh.addVertex(point[0], 0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) self.setMeshData(mesh)
def __init__(self, node, hull, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._material = None self._original_parent = parent self._inherit_orientation = False self._inherit_scale = False self._node = node self._node.transformationChanged.connect(self._onNodePositionChanged) self._node.parentChanged.connect(self._onNodeParentChanged) #self._onNodePositionChanged(self._node) self._hull = hull hull_points = self._hull.getPoints() center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh = MeshData() mesh.addVertex(center[0], 0.1, center[1]) for point in hull_points: mesh.addVertex(point[0], 0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) self.setMeshData(mesh)
def read(self, file_name): mesh = None scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: mesh = MeshData() scene_node = SceneNode() f = open(file_name, "rb") if not self._loadBinary(mesh, f): f.close() f = open(file_name, "rt") try: self._loadAscii(mesh, f) except UnicodeDecodeError: pass f.close() f.close() mesh.calculateNormals(fast = True) Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount()) scene_node.setMeshData(mesh) return scene_node
def read(self, file_name): mesh = None scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: mesh = MeshData() scene_node = SceneNode() f = open(file_name, "rb") if not self._loadBinary(mesh, f): f.close() f = open(file_name, "rt") try: self._loadAscii(mesh, f) except UnicodeDecodeError: pass f.close() f.close() time.sleep(0.1) #Yield somewhat to ensure the GUI has time to update a bit. mesh.calculateNormals(fast = True) Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount()) scene_node.setMeshData(mesh) return scene_node
def read(self, file_name): mesh = None 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 = MeshData() scene_node.setMeshData(mesh) 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.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.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.addFace(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.setVertexUVCoordinates(mesh.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh.setVertexUVCoordinates(mesh.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh.setVertexUVCoordinates(mesh.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh.hasNormals(): mesh.calculateNormals(fast = True) return scene_node
class PathResultDecorator(SceneNodeDecorator): _move_color = Color(0, 0, 0, 128) _cut_color = Color(255, 0, 0, 255) _engrave_color = Color(0, 0, 255, 255) def __init__(self): super().__init__() self._paths = None def getCutPaths(self): return self._cut_paths def getEngravePaths(self): return self._engrave_paths def setPaths(self, engrave_paths, cut_paths): self._cut_paths = cut_paths self._engrave_paths = engrave_paths self._mesh = MeshData() last_point = None for path in engrave_paths.closed_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._engrave_color) last_point = point self._addMeshLine(last_point, path[0], self._engrave_color) last_point = path[0] for path in engrave_paths.open_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._engrave_color) last_point = point for path in cut_paths.closed_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._cut_color) last_point = point self._addMeshLine(last_point, path[0], self._cut_color) last_point = path[0] for path in cut_paths.open_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._cut_color) last_point = point self.getNode().setMeshData(self._mesh) def _addMeshLine(self, p0, p1, color): self._mesh.addVertex(p0[0] / Paths.SCALE, 0.0, p0[1] / Paths.SCALE) self._mesh.addVertex(p1[0] / Paths.SCALE, 0.0, p1[1] / Paths.SCALE) self._mesh.setVertexColor(self._mesh.getVertexCount() - 2, color) self._mesh.setVertexColor(self._mesh.getVertexCount() - 1, color)
class MeshBuilder: ## Creates a new MeshBuilder with an empty mesh. def __init__(self): self._mesh_data = MeshData() ## Gets the mesh that was built by this MeshBuilder. # # Note that this gets a reference to the mesh. Adding more primitives to # the MeshBuilder will cause the mesh returned by this function to change # as well. # # \return A mesh with all the primitives added to this MeshBuilder. def getData(self): return self._mesh_data ## Adds a 3-dimensional line to the mesh of this mesh builder. # # \param v0 One endpoint of the line to add. # \param v1 The other endpoint of the line to add. # \param color (Optional) The colour of the line, if any. If no colour is # provided, the colour is determined by the shader. def addLine(self, v0, v1, color = None): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) if color: #Add colours to the vertices, if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a triangle to the mesh of this mesh builder. # # \param v0 The first corner of the triangle. # \param v1 The second corner of the triangle. # \param v2 The third corner of the triangle. # \param normal (Optional) The normal vector for the triangle. If no # normal vector is provided, it will be calculated automatically. # \param color (Optional) The colour for the triangle. If no colour is # provided, the colour is determined by the shader. def addFace(self, v0, v1, v2, normal = None, color = None): if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) #Computes the normal by itself. if color: #Add colours to the vertices if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Add a quadrilateral to the mesh of this mesh builder. # # The quadrilateral will be constructed as two triangles. v0 and v2 are # the two vertices across the diagonal of the quadrilateral. # # \param v0 The first corner of the quadrilateral. # \param v1 The second corner of the quadrilateral. # \param v2 The third corner of the quadrilateral. # \param v3 The fourth corner of the quadrilateral. # \param normal (Optional) The normal vector for the quadrilateral. Both # triangles will get the same normal vector, if provided. If no normal # vector is provided, the normal vectors for both triangles are computed # automatically. # \param color (Optional) The colour for the quadrilateral. If no colour # is provided, the colour is determined by the shader. def addQuad(self, v0, v1, v2, v3, normal = None, color = None): self.addFace(v0, v2, v1, color = color, normal = normal ) self.addFace(v0, v3, v2, #v0 and v2 are shared with the other triangle! color = color, normal = normal ) ## Add a rectangular cuboid to the mesh of this mesh builder. # # A rectangular cuboid is a square block with arbitrary width, height and # depth. # # \param width The size of the rectangular cuboid in the X dimension. # \param height The size of the rectangular cuboid in the Y dimension. # \param depth The size of the rectangular cuboid in the Z dimension. # \param center (Optional) The position of the centre of the rectangular # cuboid in space. If not provided, the cuboid is placed at the coordinate # origin. # \param color (Optional) The colour for the rectangular cuboid. If no # colour is provided, the colour is determined by the shader. def addCube(self, width, height, depth, center = Vector(0, 0, 0), color = None): #Compute the actual positions of the planes. minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ #All 8 corners. [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ #All 6 quads (12 triangles). [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add a colour to all of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) ## Add an arc to the mesh of this mesh builder. # # An arc is a curve that is also a segment of a circle. # # \param radius The radius of the circle this arc is a segment of. # \param axis The axis perpendicular to the plane on which the arc lies. # \param angle (Optional) The length of the arc, in radians. If not # provided, the entire circle is used (2 pi). # \param center (Optional) The position of the centre of the arc in space. # If no position is provided, the arc is centred around the coordinate # origin. # \param sections (Optional) The resolution of the arc. The arc is # approximated by this number of line segments. # \param color (Optional) The colour for the arc. If no colour is # provided, the colour is determined by the shader. def addArc(self, radius, axis, angle = math.pi * 2, center = Vector(0, 0, 0), sections = 32, color = None): #We'll compute the vertices of the arc by computing an initial point and #rotating the initial point with a rotation matrix. if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = angle / sections current_angle = 0 point = start + center m = Matrix() while current_angle <= angle: #Add each of the vertices. self._mesh_data.addVertex(point.x, point.y, point.z) current_angle += angle_increment m.setByRotationAxis(current_angle, axis) point = start.multiply(m) + center #Get the next vertex by rotating the start position with a matrix. self._mesh_data.addVertex(point.x, point.y, point.z) if color: #If we have a colour, add that colour to the new vertex. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a torus to the mesh of this mesh builder. # # The torus is the shape of a doughnut. This doughnut is delicious and # moist, but not very healthy. # # \param inner_radius The radius of the hole inside the torus. Must be # smaller than outer_radius. # \param outer_radius The radius of the outside of the torus. Must be # larger than inner_radius. # \param width The radius of the torus in perpendicular direction to its # perimeter. This is the "thickness". # \param center (Optional) The position of the centre of the torus. If no # position is provided, the torus will be centred around the coordinate # origin. # \param sections (Optional) The resolution of the torus in the # circumference. The resolution of the intersection of the torus cannot be # changed. # \param color (Optional) The colour of the torus. If no colour is # provided, a colour will be determined by the shader. # \param angle (Optional) An angle of rotation to rotate the torus by, in # radians. # \param axis (Optional) An axis of rotation to rotate the torus around. # If no axis is provided and the angle of rotation is nonzero, the torus # will be rotated around the Y-axis. def addDonut(self, inner_radius, outer_radius, width, center = Vector(0, 0, 0), sections = 32, color = None, angle = 0, axis = Vector.Unit_Y): vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() #Starting index. for i in range(sections): v1 = start + i * 3 #Indices for each of the vertices we'll add for this section. v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) #Angle of this piece around torus perimeter. c = math.cos(theta) #X-coordinate around torus perimeter. s = math.sin(theta) #Y-coordinate around torus perimeter. #One vertex on the inside perimeter, two on the outside perimiter (up and down). vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) #Connect the vertices to the next segment. indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: #If we have a colour, add it to the vertices. colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) #Rotate the resulting torus around the specified axis. matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3, 0:3]) vertices[:] += center.getData() #And translate to the desired position. self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) ## Adds a pyramid to the mesh of this mesh builder. # # \param width The width of the base of the pyramid. # \param height The height of the pyramid (from base to notch). # \param depth The depth of the base of the pyramid. # \param angle (Optional) An angle of rotation to rotate the pyramid by, # in degrees. # \param axis (Optional) An axis of rotation to rotate the pyramid around. # If no axis is provided and the angle of rotation is nonzero, the pyramid # will be rotated around the Y-axis. # \param center (Optional) The position of the centre of the base of the # pyramid. If not provided, the pyramid will be placed on the coordinate # origin. # \param color (Optional) The colour of the pyramid. If no colour is # provided, a colour will be determined by the shader. def addPyramid(self, width, height, depth, angle = 0, axis = Vector.Unit_Y, center = Vector(0, 0, 0), color = None): angle = math.radians(angle) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() #Starting index. matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ #All 5 vertices of the pyramid. [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) #Rotate the pyramid around the axis. verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ #Connect the vertices to each other (6 triangles). [start, start + 1, start + 4], #The four sides of the pyramid. [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], #The base of the pyramid. [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add the colour to each of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)
def read(self, file_name): mesh = None scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh = MeshData() scene_node.setMeshData(mesh) 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) f.close() mesh.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 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.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.addFace(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.setVertexUVCoordinates(mesh.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh.setVertexUVCoordinates(mesh.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh.setVertexUVCoordinates(mesh.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) if not mesh.hasNormals(): mesh.calculateNormals(fast = True) return scene_node
def read(self, file_name): result = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: 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) for object in objects: mesh = MeshData() node = SceneNode() vertex_list = [] #for vertex in object.mesh.vertices.vertex: for vertex in object.findall(".//3mf:vertex", self._namespaces): vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) triangles = object.findall(".//3mf:triangle", self._namespaces) mesh.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.addFace(vertex_list[v1][0],vertex_list[v1][2],vertex_list[v1][1],vertex_list[v2][0],vertex_list[v2][2],vertex_list[v2][1],vertex_list[v3][0],vertex_list[v3][2],vertex_list[v3][1]) #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) node.setSelectable(True) Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount()) transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces) if transformation: transformation = transformation[0] if 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.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3))) temp_quaternion = Quaternion() temp_quaternion.setByMatrix(temp_mat) node.setOrientation(temp_quaternion) # Magical scale extraction S2 = temp_mat.getTransposed().multiply(temp_mat) scale_x = math.sqrt(S2.at(0,0)) scale_y = math.sqrt(S2.at(1,1)) scale_z = math.sqrt(S2.at(2,2)) node.setScale(Vector(scale_x,scale_y,scale_z)) # We use a different coordinate frame, so rotate. #rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) #node.rotate(rotation) result.addChild(node) #If there is more then one object, group them. try: if len(objects) > 1: group_decorator = GroupDecorator() result.addDecorator(group_decorator) except: pass except Exception as e: Logger.log("e" ,"exception occured in 3mf reader: %s" , e) return result
class MeshBuilder: def __init__(self): self._mesh_data = MeshData() def getData(self): return self._mesh_data def addLine(self, v0, v1, **kwargs): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addFace(self, v0, v1, v2, **kwargs): normal = kwargs.get("normal", None) if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addQuad(self, v0, v1, v2, v3, **kwargs): self.addFace(v0, v2, v1, color = kwargs.get("color"), normal = kwargs.get("normal") ) self.addFace(v0, v3, v2, color = kwargs.get("color"), normal = kwargs.get("normal") ) def addCube(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) def addArc(self, **kwargs): radius = kwargs["radius"] axis = kwargs["axis"] max_angle = kwargs.get("angle", math.pi * 2) center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = max_angle / sections angle = 0 point = start + center m = Matrix() while angle <= max_angle: self._mesh_data.addVertex(point.x, point.y, point.z) angle += angle_increment m.setByRotationAxis(angle, axis) point = start.multiply(m) + center self._mesh_data.addVertex(point.x, point.y, point.z) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addDonut(self, **kwargs): inner_radius = kwargs["inner_radius"] outer_radius = kwargs["outer_radius"] width = kwargs["width"] center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) angle = kwargs.get("angle", 0) axis = kwargs.get("axis", Vector.Unit_Y) vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() for i in range(sections): v1 = start + i * 3 v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) c = math.cos(theta) s = math.sin(theta) vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3,0:3]) vertices[:] += center.getData() self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) def addPyramid(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] angle = math.radians(kwargs.get("angle", 0)) axis = kwargs.get("axis", Vector.Unit_Y) center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 1, start + 4], [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)