def _updateTransformation(self): self._transformation = Matrix.fromPositionOrientationScale(self._position, self._orientation, self._scale) if self._parent: parent_orientation = self._parent._getDerivedOrientation() if self._inherit_orientation: self._derived_orientation = parent_orientation * self._orientation else: self._derived_orientation = self._orientation parent_scale = self._parent._getDerivedScale() if self._inherit_scale: self._derived_scale = parent_scale.scale(self._scale) else: self._derived_scale = self._scale self._derived_position = parent_orientation.rotate(parent_scale.scale(self._position)) self._derived_position += self._parent._getDerivedPosition() self._world_transformation = Matrix.fromPositionOrientationScale(self._derived_position, self._derived_orientation, self._derived_scale) else: self._derived_position = self._position self._derived_orientation = self._orientation self._derived_scale = self._scale self._world_transformation = self._transformation
def read(self, file_name, **kwargs): try: for id, reader in self._mesh_readers.items(): result = reader.read(file_name) if result is not None: if kwargs.get("center", True): # If the result has a mesh and no children it needs to be centered if result.getMeshData() and len(result.getChildren()) == 0: extents = result.getMeshData().getExtents() move_vector = Vector() move_vector.setX(extents.center.x) move_vector.setY(extents.center.y) # Ensure that bottom is on 0 (above plate) move_vector.setZ(extents.center.z) result.setCenterPosition(move_vector) if result.getMeshData().getExtents().bottom != 0: result.translate(Vector(0,-result.getMeshData().getExtents().bottom ,0)) # Move all the meshes of children so that toolhandles are shown in the correct place. for node in result.getChildren(): if node.getMeshData(): extents = node.getMeshData().getExtents() m = Matrix() m.translate(-extents.center) node.setMeshData(node.getMeshData().getTransformed(m)) node.translate(extents.center) return result except OSError as e: Logger.log("e", str(e)) Logger.log("w", "Unable to read file %s", file_name) return None #unable to read
def _rotateCamera(self, x, y): camera = self._scene.getActiveCamera() if not camera or not camera.isEnabled(): return self._scene.acquireLock() dx = math.radians(x * 180.0) dy = math.radians(y * 180.0) diff = camera.getPosition() - self._origin diff_flat = Vector(diff.x, 0.0, diff.z).getNormalized() try: new_angle = math.acos(diff_flat.dot(diff.getNormalized())) + dy except ValueError: return m = Matrix() m.setByRotationAxis(dx, Vector.Unit_Y) if new_angle < (math.pi / 2 - 0.01): m.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalize()) n = diff.multiply(m) n += self._origin camera.setPosition(n) camera.lookAt(self._origin) self._scene.releaseLock()
def read(self, file_name, storage_device): extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: loaded_workspace = SceneNode() mesh_handler = Application.getInstance().getMeshFileHandler() f = storage_device.openFile(file_name, "rt") tree = ET.parse(f) root = tree.getroot() if root.tag == "MeshLabProject": for group in root.findall("MeshGroup"): for mesh in group.findall("MLMesh"): mesh_data = mesh_handler.read(mesh.get("filename"),Application.getInstance().getStorageDevice("local")) mesh_data.setName(mesh.get("label")) if mesh_data.getType() is MeshType.pointcloud: mesh_node = PointCloudNode(loaded_workspace) else: mesh_node = SceneNode(loaded_workspace) mesh_lines = mesh.findall("MLMatrix44")[0].text.splitlines() mesh_matrix = Matrix() mesh_matrix.setColumn(0,mesh_lines[1].split()) mesh_matrix.setColumn(1,mesh_lines[2].split()) mesh_matrix.setColumn(2,mesh_lines[3].split()) mesh_matrix.setColumn(3,mesh_lines[4].split()) mesh_node.setMeshData(mesh_data) return loaded_workspace else: Logger.log("e", "Unable to load file. It seems to be formated wrong.") return None else: return None # Can't do anything with provided extention
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 _updateTransformation(self): scale_and_mirror = self._scale * self._mirror self._transformation = Matrix.fromPositionOrientationScale(self._position, self._orientation, scale_and_mirror) if self._parent: parent_orientation = self._parent._getDerivedOrientation() if self._inherit_orientation: self._derived_orientation = parent_orientation * self._orientation else: self._derived_orientation = self._orientation # Sometimes the derived orientation can be None. # I've not been able to locate the cause of this, but this prevents it being an issue. if not self._derived_orientation: self._derived_orientation = Quaternion() parent_scale = self._parent._getDerivedScale() if self._inherit_scale: self._derived_scale = parent_scale.scale(scale_and_mirror) else: self._derived_scale = scale_and_mirror self._derived_position = parent_orientation.rotate(parent_scale.scale(self._position)) self._derived_position += self._parent._getDerivedPosition() self._world_transformation = Matrix.fromPositionOrientationScale(self._derived_position, self._derived_orientation, self._derived_scale) else: self._derived_position = self._position self._derived_orientation = self._orientation self._derived_scale = scale_and_mirror self._world_transformation = self._transformation
def readerRead(self, reader, file_name, **kwargs): try: results = reader.read(file_name) if results is not None: if type(results) is not list: results = [results] for result in results: if kwargs.get("center", True): # If the result has a mesh and no children it needs to be centered if result.getMeshData() and len(result.getChildren()) == 0: extents = result.getMeshData().getExtents() move_vector = Vector(extents.center.x, extents.center.y, extents.center.z) result.setCenterPosition(move_vector) # Move all the meshes of children so that toolhandles are shown in the correct place. for node in result.getChildren(): if node.getMeshData(): extents = node.getMeshData().getExtents() m = Matrix() m.translate(-extents.center) node.setMeshData(node.getMeshData().getTransformed(m)) node.translate(extents.center) return results except OSError as e: Logger.log("e", str(e)) Logger.log("w", "Unable to read file %s", file_name) return None # unable to read
def processTransform(self, node): rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple trans = readVector(node, "translation", (0, 0, 0)) # Vector scale = readVector(node, "scale", (1, 1, 1)) # Vector center = readVector(node, "center", (0, 0, 0)) # Vector scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple # Store the previous transform; in Cura, the default matrix multiplication is in place prev = Matrix(self.transform.getData()) # It's deep copy, I've checked # The rest of transform manipulation will be applied in place got_center = (center.x != 0 or center.y != 0 or center.z != 0) T = self.transform if trans.x != 0 or trans.y != 0 or trans.z !=0: T.translate(trans) if got_center: T.translate(center) if rot[0] != 0: T.rotateByAxis(*rot) if scale.x != 1 or scale.y != 1 or scale.z != 1: got_scale_orient = scale_orient[0] != 0 if got_scale_orient: T.rotateByAxis(*scale_orient) # No scale by vector in place operation in UM S = Matrix() S.setByScaleVector(scale) T.multiply(S) if got_scale_orient: T.rotateByAxis(-scale_orient[0], scale_orient[1]) if got_center: T.translate(-center) self.processChildNodes(node) self.transform = prev
def test_getterAndSetters(): # Pretty much all of them are super simple, but it doesn't hurt to check them. camera = Camera() camera.setAutoAdjustViewPort(False) assert camera.getAutoAdjustViewPort() == False camera.setViewportWidth(12) assert camera.getViewportWidth() == 12 camera.setViewportHeight(12) assert camera.getViewportHeight() == 12 camera.setViewportSize(22, 22) assert camera.getViewportHeight() == 22 assert camera.getViewportWidth() == 22 camera.setWindowSize(9001, 9002) assert camera.getWindowSize() == (9001, 9002) camera.setPerspective(False) assert camera.isPerspective() == False matrix = Matrix() matrix.setPerspective(10, 20, 30, 40) camera.setProjectionMatrix(matrix) assert numpy.array_equal(camera.getProjectionMatrix().getData(), matrix.getData())
def run(self): loading_message = Message(i18n_catalog.i18nc("Loading mesh message, {0} is file name", "Loading {0}").format(self._filename), lifetime = 0, dismissable = False) loading_message.setProgress(-1) loading_message.show() mesh = self._handler.read(self._filename, self._device) # Scale down to maximum bounds size if that is available if hasattr(Application.getInstance().getController().getScene(), "_maximum_bounds"): max_bounds = Application.getInstance().getController().getScene()._maximum_bounds bbox = mesh.getExtents() if max_bounds.width < bbox.width or max_bounds.height < bbox.height or max_bounds.depth < bbox.depth: largest_dimension = max(bbox.width, bbox.height, bbox.depth) scale_factor = 1.0 if largest_dimension == bbox.width: scale_factor = max_bounds.width / bbox.width elif largest_dimension == bbox.height: scale_factor = max_bounds.height / bbox.height else: scale_factor = max_bounds.depth / bbox.depth matrix = Matrix() matrix.setByScaleFactor(scale_factor) mesh = mesh.getTransformed(matrix) self.setResult(mesh) loading_message.hide() result_message = Message(i18n_catalog.i18nc("Finished loading mesh message, {0} is file name", "Loaded {0}").format(self._filename)) result_message.show()
def setCenterPosition(self, center: Vector): if self._mesh_data: m = Matrix() m.setByTranslation(-center) self._mesh_data = self._mesh_data.getTransformed(m).set(center_position=center) for child in self._children: child.setCenterPosition(center)
def test_deepcopy(self): matrix = Matrix() # Set some data matrix.setRow(1, [1, 2, 3]) matrix.setColumn(2, [3, 4, 5]) copied_matrix = copy.deepcopy(matrix) assert copied_matrix == matrix
def test_compare(self): matrix = Matrix() matrix2 = Matrix() assert matrix == matrix assert not matrix == "zomg" matrix._data = None matrix2._data = None assert matrix == matrix2
def test_preMultiply(self): temp_matrix = Matrix() temp_matrix.setByTranslation(Vector(10,10,10)) temp_matrix2 = Matrix() temp_matrix2.setByScaleFactor(0.5) temp_matrix.preMultiply(temp_matrix2) numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,5],[0,0.5,0,5],[0,0,0.5,5],[0,0,0,1]]))
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.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.addVertices(vertices) self.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self.addColors(numpy.asarray(colors, dtype = numpy.float32))
def test_transformMeshData(): transformation_matrix = Matrix() transformation_matrix.setByTranslation(Vector(30, 20, 10)) vertices = numpy.zeros((1, 3), dtype=numpy.float32) mesh_data = MeshData(vertices) transformed_mesh = mesh_data.getTransformed(transformation_matrix) assert transformed_mesh.getVertex(0)[0] == 30. assert transformed_mesh.getVertex(0)[1] == 20. assert transformed_mesh.getVertex(0)[2] == 10.
def setUp(self): # Called before the first testfunction is executed self._scene = Scene() self._scene_object = SceneNode() self._scene_object2 = SceneNode() self._scene_object.addChild(self._scene_object2) self._scene.getRoot().addChild(self._scene_object) temp_matrix = Matrix() temp_matrix.setByTranslation(Vector(10, 10, 10)) self._scene_object2.setLocalTransformation(deepcopy(temp_matrix)) temp_matrix.setByScaleFactor(0.5) self._scene_object.setLocalTransformation(temp_matrix)
def test_fromMatrix(self): m = Matrix() m.setByRotationAxis(math.pi / 2, Vector.Unit_Z) q1 = Quaternion.fromMatrix(m) q2 = Quaternion() q2.setByAngleAxis(math.pi / 2, Vector.Unit_Z) self.assertTrue(Float.fuzzyCompare(q1.x, q2.x, 1e-6)) self.assertTrue(Float.fuzzyCompare(q1.y, q2.y, 1e-6)) self.assertTrue(Float.fuzzyCompare(q1.z, q2.z, 1e-6)) self.assertTrue(Float.fuzzyCompare(q1.w, q2.w, 1e-6))
def translate(self, translation, transform_space = TransformSpace.Local): if not self._enabled: return translation_matrix = Matrix() translation_matrix.setByTranslation(translation) if transform_space == SceneNode.TransformSpace.Local: self._transformation.multiply(translation_matrix) elif transform_space == SceneNode.TransformSpace.Parent: self._transformation.preMultiply(translation_matrix) elif transform_space == SceneNode.TransformSpace.World: self._transformation.multiply(self._world_transformation.getInverse()) self._transformation.multiply(translation_matrix) self._transformation.multiply(self._world_transformation) self._transformChanged()
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "") -> None: super().__init__() # Call super to make multiple inheritance work. self._children = [] # type: List[SceneNode] self._mesh_data = None # type: Optional[MeshData] # Local transformation (from parent to local) self._transformation = Matrix() # type: Matrix # Convenience "components" of the transformation self._position = Vector() # type: Vector self._scale = Vector(1.0, 1.0, 1.0) # type: Vector self._shear = Vector(0.0, 0.0, 0.0) # type: Vector self._mirror = Vector(1.0, 1.0, 1.0) # type: Vector self._orientation = Quaternion() # type: Quaternion # World transformation (from root to local) self._world_transformation = Matrix() # type: Matrix # Convenience "components" of the world_transformation self._derived_position = Vector() # type: Vector self._derived_orientation = Quaternion() # type: Quaternion self._derived_scale = Vector() # type: Vector self._parent = parent # type: Optional[SceneNode] # Can this SceneNode be modified in any way? self._enabled = True # type: bool # Can this SceneNode be selected in any way? self._selectable = False # type: bool # Should the AxisAlignedBoundingBox be re-calculated? self._calculate_aabb = True # type: bool # The AxisAligned bounding box. self._aabb = None # type: Optional[AxisAlignedBox] self._bounding_box_mesh = None # type: Optional[MeshData] self._visible = visible # type: bool self._name = name # type: str self._decorators = [] # type: List[SceneNodeDecorator] # Store custom settings to be compatible with Savitar SceneNode self._settings = {} # type: Dict[str, Any] ## Signals self.parentChanged.connect(self._onParentChanged) if parent: parent.addChild(self)
def _updateViewportGeometry(self, width, height): view_w = width * self._viewport_rect.width() view_h = height * self._viewport_rect.height() for camera in self._app.getController().getScene().getAllCameras(): camera.setViewportSize(view_w, view_h) proj = Matrix() if camera.isPerspective(): proj.setPerspective(30, view_w / view_h, 1, 500) else: proj.setOrtho(-view_w / 2, view_w / 2, -view_h / 2, view_h / 2, -500, 500) camera.setProjectionMatrix(proj) self._app.getRenderer().setViewportSize(view_w, view_h) self._app.getRenderer().setWindowSize(width, height)
def __init__(self, parent = None, **kwargs): super().__init__() # Call super to make multiple inheritance work. self._children = [] # type: List[SceneNode] self._mesh_data = None # type: MeshData # Local transformation (from parent to local) self._transformation = Matrix() # type: Matrix # Convenience "components" of the transformation self._position = Vector() # type: Vector self._scale = Vector(1.0, 1.0, 1.0) # type: Vector self._shear = Vector(0.0, 0.0, 0.0) # type: Vector self._mirror = Vector(1.0, 1.0, 1.0) # type: Vector self._orientation = Quaternion() # type: Quaternion # World transformation (from root to local) self._world_transformation = Matrix() # type: Matrix # Convenience "components" of the world_transformation self._derived_position = Vector() # type: Vector self._derived_orientation = Quaternion() # type: Quaternion self._derived_scale = Vector() # type: Vector self._parent = parent # type: Optional[SceneNode] # Can this SceneNode be modified in any way? self._enabled = True # type: bool # Can this SceneNode be selected in any way? self._selectable = False # type: bool # Should the AxisAlignedBounxingBox be re-calculated? self._calculate_aabb = True # type: bool # The AxisAligned bounding box. self._aabb = None # type: Optional[AxisAlignedBox] self._bounding_box_mesh = None # type: Optional[MeshData] self._visible = kwargs.get("visible", True) # type: bool self._name = kwargs.get("name", "") # type: str self._decorators = [] # type: List[SceneNodeDecorator] ## Signals self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh) self.parentChanged.connect(self._onParentChanged) if parent: parent.addChild(self)
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 __init__(self, node, translation = None, orientation = None, scale = None, shear = None, mirror = None): super().__init__() self._node = node self._old_translation = node.getPosition() self._old_orientation = node.getOrientation() self._old_scale = node.getScale() self._old_shear = node.getShear() self._old_transformation = node.getWorldTransformation() if translation: self._new_translation = translation else: self._new_translation = node.getPosition() if orientation: self._new_orientation = orientation else: self._new_orientation = node.getOrientation() if scale: self._new_scale = scale else: self._new_scale = node.getScale() if shear: self._new_shear = shear else: self._new_shear = node.getShear() self._new_mirror = Vector(1, 1, 1) self._new_transformation = Matrix() euler_orientation = self._new_orientation.toMatrix().getEuler() self._new_transformation.compose(scale = self._new_scale, shear = self._new_shear, angles = euler_orientation, translate = self._new_translation, mirror = self._new_mirror)
def scale(self, scale: Vector, transform_space: int = TransformSpace.Local): if not self._enabled: return scale_matrix = Matrix() scale_matrix.setByScaleVector(scale) if transform_space == SceneNode.TransformSpace.Local: self._transformation.multiply(scale_matrix) elif transform_space == SceneNode.TransformSpace.Parent: self._transformation.preMultiply(scale_matrix) elif transform_space == SceneNode.TransformSpace.World: self._transformation.multiply(self._world_transformation.getInverse()) self._transformation.multiply(scale_matrix) self._transformation.multiply(self._world_transformation) self._transformChanged()
def resizeEvent(self, event): super().resizeEvent(event) w = event.size().width() * self.devicePixelRatio() h = event.size().height() * self.devicePixelRatio() for camera in self._app.getController().getScene().getAllCameras(): camera.setViewportSize(w, h) proj = Matrix() if camera.isPerspective(): proj.setPerspective(30, w/h, 1, 500) else: proj.setOrtho(-w / 2, w / 2, -h / 2, h / 2, -500, 500) camera.setProjectionMatrix(proj) self._preferences.setValue("general/window_width", event.size().width()) self._preferences.setValue("general/window_height", event.size().height()) self._app.getRenderer().setViewportSize(w, h)
def resizeEvent(self, event): super().resizeEvent(event) w = event.size().width() * self.devicePixelRatio() h = event.size().height() * self.devicePixelRatio() for camera in self._app.getController().getScene().getAllCameras(): camera.setViewportSize(w, h) proj = Matrix() if camera.isPerspective(): proj.setPerspective(30, w/h, 1, 500) else: proj.setOrtho(-w / 2, w / 2, -h / 2, h / 2, -500, 500) camera.setProjectionMatrix(proj) self._app.getRenderer().setViewportSize(w, h) QMetaObject.invokeMethod(self, "_onWindowGeometryChanged", Qt.QueuedConnection);
def setOrientation(self, orientation, transform_space = TransformSpace.Local): if not self._enabled or orientation == self._orientation: return new_transform_matrix = Matrix() if transform_space == SceneNode.TransformSpace.Local: orientation_matrix = orientation.toMatrix() if transform_space == SceneNode.TransformSpace.World: if self.getWorldOrientation() == orientation: return new_orientation = orientation * (self.getWorldOrientation() * self._orientation.getInverse()).getInverse() orientation_matrix = new_orientation.toMatrix() euler_angles = orientation_matrix.getEuler() new_transform_matrix.compose(scale = self._scale, angles = euler_angles, translate = self._position, shear = self._shear) self._transformation = new_transform_matrix self._transformChanged()
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)
def _rotateCamera(self, x, y): camera = self._scene.getActiveCamera() if not camera or not camera.isEnabled(): return self._scene.acquireLock() dx = math.radians(x * 180.0) dy = math.radians(y * 180.0) diff = camera.getPosition() - self._origin my = Matrix() my.setByRotationAxis(dx, Vector.Unit_Y) mx = Matrix(my.getData()) mx.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalized()) n = diff.multiply(mx) try: angle = math.acos(Vector.Unit_Y.dot(n.normalized())) except ValueError: return if angle < 0.1 or angle > math.pi - 0.1: n = diff.multiply(my) n += self._origin camera.setPosition(n) camera.lookAt(self._origin) self._scene.releaseLock()
def write(self, stream, nodes, mode=MeshWriter.OutputMode.BinaryMode): self._archive = None # Reset archive archive = zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) try: model_file = zipfile.ZipInfo("3D/3dmodel.model") # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo. model_file.compress_type = zipfile.ZIP_DEFLATED # Create content types file content_types_file = zipfile.ZipInfo("[Content_Types].xml") content_types_file.compress_type = zipfile.ZIP_DEFLATED content_types = ET.Element("Types", xmlns=self._namespaces["content-types"]) rels_type = ET.SubElement( content_types, "Default", Extension="rels", ContentType= "application/vnd.openxmlformats-package.relationships+xml") model_type = ET.SubElement( content_types, "Default", Extension="model", ContentType= "application/vnd.ms-package.3dmanufacturing-3dmodel+xml") # Create _rels/.rels file relations_file = zipfile.ZipInfo("_rels/.rels") relations_file.compress_type = zipfile.ZIP_DEFLATED relations_element = ET.Element( "Relationships", xmlns=self._namespaces["relationships"]) model_relation_element = ET.SubElement( relations_element, "Relationship", Target="/3D/3dmodel.model", Id="rel0", Type= "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") savitar_scene = Savitar.Scene() metadata_to_store = CuraApplication.getInstance().getController( ).getScene().getMetaData() for key, value in metadata_to_store.items(): savitar_scene.setMetaDataEntry(key, value) current_time_string = datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S") if "Application" not in metadata_to_store: # This might sound a bit strange, but this field should store the original application that created # the 3mf. So if it was already set, leave it to whatever it was. savitar_scene.setMetaDataEntry( "Application", CuraApplication.getInstance().getApplicationDisplayName()) if "CreationDate" not in metadata_to_store: savitar_scene.setMetaDataEntry("CreationDate", current_time_string) savitar_scene.setMetaDataEntry("ModificationDate", current_time_string) transformation_matrix = Matrix() transformation_matrix._data[1, 1] = 0 transformation_matrix._data[1, 2] = -1 transformation_matrix._data[2, 1] = 1 transformation_matrix._data[2, 2] = 0 global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the # build volume. if global_container_stack: translation_vector = Vector( x=global_container_stack.getProperty( "machine_width", "value") / 2, y=global_container_stack.getProperty( "machine_depth", "value") / 2, z=0) translation_matrix = Matrix() translation_matrix.setByTranslation(translation_vector) transformation_matrix.preMultiply(translation_matrix) root_node = UM.Application.Application.getInstance().getController( ).getScene().getRoot() for node in nodes: if node == root_node: for root_child in node.getChildren(): savitar_node = self._convertUMNodeToSavitarNode( root_child, transformation_matrix) if savitar_node: savitar_scene.addSceneNode(savitar_node) else: savitar_node = self._convertUMNodeToSavitarNode( node, transformation_matrix) if savitar_node: savitar_scene.addSceneNode(savitar_node) parser = Savitar.ThreeMFParser() scene_string = parser.sceneToString(savitar_scene) archive.writestr(model_file, scene_string) archive.writestr( content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types)) archive.writestr( relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element)) except Exception as e: Logger.logException("e", "Error writing zip file") self.setInformation( catalog.i18nc("@error:zip", "Error writing 3mf file.")) return False finally: if not self._store_archive: archive.close() else: self._archive = archive return True
def _getSVDRotationFromMatrix(self, matrix): result = Matrix() rotation_data = matrix.getData()[:3, :3] U, s, Vh = scipy.linalg.svd(rotation_data) result._data[:3, :3] = U.dot(Vh) return result
class Camera(SceneNode.SceneNode): def __init__(self, name, parent=None): super().__init__(parent) self._name = name self._projection_matrix = Matrix() self._projection_matrix.setOrtho(-5, 5, 5, -5, -100, 100) self._perspective = False self._viewport_width = 0 self._viewport_height = 0 self._window_width = 0 self._window_height = 0 self.setCalculateBoundingBox(False) ## Get the projection matrix of this camera. def getProjectionMatrix(self): return copy.deepcopy(self._projection_matrix) def getViewportWidth(self): return self._viewport_width def setViewportWidth(self, width): self._viewport_width = width def setViewPortHeight(self, height): self._viewport_height = height def setViewportSize(self, width, height): self._viewport_width = width self._viewport_height = height def getViewportHeight(self): return self._viewport_height def setWindowSize(self, w, h): self._window_width = w self._window_height = h ## Set the projection matrix of this camera. # \param matrix The projection matrix to use for this camera. def setProjectionMatrix(self, matrix): self._projection_matrix = matrix def isPerspective(self): return self._perspective def setPerspective(self, pers): self._perspective = pers ## Get a ray from the camera into the world. # # This will create a ray from the camera's origin, passing through (x, y) # on the near plane and continuing based on the projection matrix. # # \param x The X coordinate on the near plane this ray should pass through. # \param y The Y coordinate on the near plane this ray should pass through. # # \return A Ray object representing a ray from the camera origin through X, Y. # # \note The near-plane coordinates should be in normalized form, that is within (-1, 1). def getRay(self, x, y): window_x = ((x + 1) / 2) * self._window_width window_y = ((y + 1) / 2) * self._window_height view_x = (window_x / self._viewport_width) * 2 - 1 view_y = (window_y / self._viewport_height) * 2 - 1 inverted_projection = numpy.linalg.inv( self._projection_matrix.getData().copy()) transformation = self.getWorldTransformation().getData() near = numpy.array([view_x, -view_y, -1.0, 1.0], dtype=numpy.float32) near = numpy.dot(inverted_projection, near) near = numpy.dot(transformation, near) near = near[0:3] / near[3] far = numpy.array([view_x, -view_y, 1.0, 1.0], dtype=numpy.float32) far = numpy.dot(inverted_projection, far) far = numpy.dot(transformation, far) far = far[0:3] / far[3] dir = far - near dir /= numpy.linalg.norm(dir) return Ray(self.getWorldPosition(), Vector(-dir[0], -dir[1], -dir[2])) ## Project a 3D position onto the 2D view plane. def project(self, position): projection = self._projection_matrix view = self.getWorldTransformation().getInverse() position = position.preMultiply(view) position = position.preMultiply(projection) return position.x / position.z / 2.0, position.y / position.z / 2.0
def _renderItem(self, item: Dict): transformation = item["transformation"] mesh = item["mesh"] # Do not render if there's no vertex (empty mesh) if mesh.getVertexCount() == 0: return normal_matrix = None if mesh.hasNormals(): normal_matrix = Matrix(transformation.getData()) normal_matrix.setRow(3, [0, 0, 0, 1]) normal_matrix.setColumn(3, [0, 0, 0, 1]) normal_matrix.invert() normal_matrix.transpose() self._shader.updateBindings(model_matrix=transformation, normal_matrix=normal_matrix) if item["uniforms"] is not None: self._shader.updateBindings(**item["uniforms"]) vertex_buffer = OpenGL.getInstance().createVertexBuffer(mesh) vertex_buffer.bind() if self._render_range is None: index_buffer = OpenGL.getInstance().createIndexBuffer(mesh) else: # glDrawRangeElements does not work as expected and did not get the indices field working.. # Now we're just uploading a clipped part of the array and the start index always becomes 0. index_buffer = OpenGL.getInstance().createIndexBuffer( mesh, force_recreate=True, index_start=self._render_range[0], index_stop=self._render_range[1]) if index_buffer is not None: index_buffer.bind() self._shader.enableAttribute("a_vertex", "vector3f", 0) offset = mesh.getVertexCount() * 3 * 4 if mesh.hasNormals(): self._shader.enableAttribute("a_normal", "vector3f", offset) offset += mesh.getVertexCount() * 3 * 4 if mesh.hasColors(): self._shader.enableAttribute("a_color", "vector4f", offset) offset += mesh.getVertexCount() * 4 * 4 if mesh.hasUVCoordinates(): self._shader.enableAttribute("a_uvs", "vector2f", offset) offset += mesh.getVertexCount() * 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"]) if mesh.hasIndices(): if self._render_range is None: if self._render_mode == self.RenderMode.Triangles: self._gl.glDrawElements(self._render_mode, mesh.getFaceCount() * 3, self._gl.GL_UNSIGNED_INT, None) else: self._gl.glDrawElements(self._render_mode, mesh.getFaceCount(), self._gl.GL_UNSIGNED_INT, None) else: if self._render_mode == self.RenderMode.Triangles: self._gl.glDrawRangeElements( self._render_mode, self._render_range[0], self._render_range[1], self._render_range[1] - self._render_range[0], self._gl.GL_UNSIGNED_INT, None) else: self._gl.glDrawElements( self._render_mode, self._render_range[1] - self._render_range[0], self._gl.GL_UNSIGNED_INT, None) else: self._gl.glDrawArrays(self._render_mode, 0, mesh.getVertexCount()) self._shader.disableAttribute("a_vertex") self._shader.disableAttribute("a_normal") self._shader.disableAttribute("a_color") self._shader.disableAttribute("a_uvs") for attribute_name in mesh.attributeNames(): attribute = mesh.getAttribute(attribute_name) self._shader.disableAttribute(attribute.get("opengl_name")) vertex_buffer.release() if index_buffer is not None: index_buffer.release()
def read(self, file_name): result = [] # The base object of 3mf is a zipped archive. try: archive = zipfile.ZipFile(file_name, "r") self._base_name = os.path.basename(file_name) parser = Savitar.ThreeMFParser() scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read()) self._unit = scene_3mf.getUnit() for node in scene_3mf.getSceneNodes(): um_node = self._convertSavitarNodeToUMNode(node) if um_node is None: continue # compensate for original center position, if object(s) is/are not around its zero position transform_matrix = Matrix() mesh_data = um_node.getMeshData() if mesh_data is not None: extents = mesh_data.getExtents() center_vector = Vector(extents.center.x, extents.center.y, extents.center.z) transform_matrix.setByTranslation(center_vector) transform_matrix.multiply(um_node.getLocalTransformation()) um_node.setTransformation(transform_matrix) global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Create a transformation Matrix to convert from 3mf worldspace into ours. # First step: flip the y and z axis. transformation_matrix = Matrix() transformation_matrix._data[1, 1] = 0 transformation_matrix._data[1, 2] = 1 transformation_matrix._data[2, 1] = -1 transformation_matrix._data[2, 2] = 0 # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the # build volume. if global_container_stack: translation_vector = Vector( x=-global_container_stack.getProperty( "machine_width", "value") / 2, y=-global_container_stack.getProperty( "machine_depth", "value") / 2, z=0) translation_matrix = Matrix() translation_matrix.setByTranslation(translation_vector) transformation_matrix.multiply(translation_matrix) # Third step: 3MF also defines a unit, whereas Cura always assumes mm. scale_matrix = Matrix() scale_matrix.setByScaleVector( self._getScaleFromUnit(self._unit)) transformation_matrix.multiply(scale_matrix) # Pre multiply the transformation with the loaded transformation, so the data is handled correctly. um_node.setTransformation( um_node.getLocalTransformation().preMultiply( transformation_matrix)) # Check if the model is positioned below the build plate and honor that when loading project files. if um_node.getMeshData() is not None: minimum_z_value = um_node.getMeshData().getExtents( um_node.getWorldTransformation( )).minimum.y # y is z in transformation coordinates if minimum_z_value < 0: um_node.addDecorator(ZOffsetDecorator()) um_node.callDecoration("setZOffset", minimum_z_value) result.append(um_node) except Exception: Logger.logException("e", "An exception occurred in 3mf reader.") return [] return result
def _createMatrixFromTransformationString(self, transformation): if transformation == "": return Matrix() splitted_transformation = transformation.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] return temp_mat
def updateSceneFromOptimizationResult( self, analysis: pywim.smartslice.result.Analysis): 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: active_extruder.setProperty(key, "value", value, 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: op = GroupedOperation() for node in mod_meshes: node.addDecorator(SmartSliceRemovedDecorator()) op.addOperation(RemoveSceneNodeOperation(node)) op.push() 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 parent_transformation = our_only_node.getLocalTransformation() modifier_mesh_transform_matrix = parent_transformation.multiply( Matrix(modifier_mesh.transform)) modifier_mesh_node.setTransformation( modifier_mesh_transform_matrix) # 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) new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", value) 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( modifier_mesh_node, Application.getInstance().getController().getScene(). getRoot())) op.addOperation( SetParentOperation( modifier_mesh_node, Application.getInstance().getController().getScene(). getRoot())) op.push() # emit changes and connect error tracker Application.getInstance().getController().getScene( ).sceneChanged.emit(modifier_mesh_node)
class SetTransformOperation(Operation.Operation): """Operation that translates, rotates and scales a node all at once.""" def __init__(self, node, translation=None, orientation=None, scale=None, shear=None, mirror=None): """Creates the transform operation. Careful! No real input checking is done by this function. If you'd provide other transformations than respectively translation, orientation and scale in place for the translation, orientation and scale matrices, it could get confused. :param node: The scene node to transform. :param translation: A translation matrix to move the node with. :param orientation: An orientation matrix to rotate the node with. :param scale: A scaling matrix to resize the node with. """ super().__init__() self._node = node self._old_translation = node.getPosition() self._old_orientation = node.getOrientation() self._old_scale = node.getScale() self._old_shear = node.getShear() self._old_transformation = node.getWorldTransformation() if translation: self._new_translation = translation else: self._new_translation = node.getPosition() if orientation: self._new_orientation = orientation else: self._new_orientation = node.getOrientation() if scale: self._new_scale = scale else: self._new_scale = node.getScale() if shear: self._new_shear = shear else: self._new_shear = node.getShear() self._new_mirror = Vector(1, 1, 1) self._new_transformation = Matrix() euler_orientation = self._new_orientation.toMatrix().getEuler() self._new_transformation.compose(scale=self._new_scale, shear=self._new_shear, angles=euler_orientation, translate=self._new_translation, mirror=self._new_mirror) def undo(self): """Undoes the transformation, restoring the node to the old state.""" self._node.setTransformation(self._old_transformation) def redo(self): """Re-applies the transformation after it has been undone.""" self._node.setTransformation(self._new_transformation) def mergeWith(self, other): """Merges this operation with another TransformOperation. This prevents the user from having to undo multiple operations if they were not his operations. You should ONLY merge this operation with an older operation. It is NOT symmetric. :param other: The older operation with which to merge this operation. :return: A combination of the two operations, or False if the merge failed. """ if type(other) is not SetTransformOperation: return False if other._node != self._node: # Must be on the same node. return False op = SetTransformOperation(self._node) op._old_transformation = other._old_transformation op._new_transformation = self._new_transformation return op def __repr__(self): """Returns a programmer-readable representation of this operation. A programmer-readable representation of this operation. """ return "SetTransformOp.(node={0})".format(self._node)
def _convertUMNodeToSavitarNode(self, um_node, transformation=Matrix()): """Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode :returns: Uranium Scene node. """ if not isinstance(um_node, SceneNode): return None active_build_plate_nr = CuraApplication.getInstance( ).getMultiBuildPlateModel().activeBuildPlate if um_node.callDecoration( "getBuildPlateNumber") != active_build_plate_nr: return savitar_node = Savitar.SceneNode() savitar_node.setName(um_node.getName()) node_matrix = um_node.getLocalTransformation() matrix_string = self._convertMatrixToString( node_matrix.preMultiply(transformation)) savitar_node.setTransformation(matrix_string) mesh_data = um_node.getMeshData() if mesh_data is not None: savitar_node.getMeshData().setVerticesFromBytes( mesh_data.getVerticesAsByteArray()) indices_array = mesh_data.getIndicesAsByteArray() if indices_array is not None: savitar_node.getMeshData().setFacesFromBytes(indices_array) else: savitar_node.getMeshData().setFacesFromBytes( numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tostring()) # Handle per object settings (if any) stack = um_node.callDecoration("getStack") if stack is not None: changed_setting_keys = stack.getTop().getAllKeys() # Ensure that we save the extruder used for this object in a multi-extrusion setup if stack.getProperty("machine_extruder_count", "value") > 1: changed_setting_keys.add("extruder_nr") # Get values for all changed settings & save them. for key in changed_setting_keys: savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value"))) # Store the metadata. for key, value in um_node.metadata.items(): savitar_node.setSetting(key, value) for child_node in um_node.getChildren(): # only save the nodes on the active build plate if child_node.callDecoration( "getBuildPlateNumber") != active_build_plate_nr: continue savitar_child_node = self._convertUMNodeToSavitarNode(child_node) if savitar_child_node is not None: savitar_node.addChild(savitar_child_node) return savitar_node