def _translateCamera(tx: int, ty: int, tz: int) -> None: camera = SpaceMouseTool._scene.getActiveCamera() if not camera or not camera.isEnabled(): Logger.log("d", "No camera available") return moveVec = Vector(0, 0, 0) # Translate camera by tx and tz. We have to reverse x and use z as y # in order to achieve the default behavior of the space mouse (c.f. # cube example of 3DX). If you prefer it another way change the setting # of the 3D mouse moveVec = moveVec.set(x=-tx, y=tz) # Zoom camera using negated ty. Again you should/can change this # behavior for space mouse if camera.isPerspective(): moveVec = moveVec.set(z=-ty) camera.translate(SpaceMouseTool._transScale * moveVec) else: # orthographic camera.translate(SpaceMouseTool._transScale * moveVec) zoomFactor = camera.getZoomFactor( ) - SpaceMouseTool._zoomScale * ty # clamp to [zoomMin, zoomMax] zoomFactor = min(SpaceMouseTool._zoomMax, max(SpaceMouseTool._zoomMin, zoomFactor)) camera.setZoomFactor(zoomFactor)
def test_setValues(self): x = 10 y = 10 z = 10 temp_vector = Vector(x,y,z) numpy.testing.assert_array_almost_equal(temp_vector.getData(), numpy.array([x,y,z])) temp_vector2 = temp_vector.set(1, 2, 3) numpy.testing.assert_array_almost_equal(temp_vector2.getData(), numpy.array([1, 2, 3]))
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() for node in BreadthFirstIterator(root): if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001) node._outside_buildarea = False # Mark the node as outside the build volume if the bounding box test fails. if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True # Move it downwards if bottom is above platform move_vector = Vector() if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 if bbox.bottom > 0: move_vector = move_vector.set(y=-bbox.bottom + z_offset) elif bbox.bottom < z_offset: move_vector = move_vector.set(y=(-bbox.bottom) - z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) node.callDecoration("recomputeConvexHull") if Preferences.getInstance().getValue("physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or type(other_node) is not SceneNode or other_node is node: continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren() or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None: continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox(): continue # Get the overlap distance for both convex hulls. If this returns None, there is no intersection. head_hull = node.callDecoration("getConvexHullHead") if head_hull: overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.intersectsPolygon(other_convex_hull) else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None if overlap is None: continue move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1) convex_hull = node.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): return # Check for collisions between disallowed areas and the object for area in self._build_volume.getDisallowedAreas(): overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push()
def event(self, event): """Handle mouse and keyboard events :param event: type(Event) """ super().event(event) if event.type == Event.ToolActivateEvent: for node in self._getSelectedObjectsWithoutSelectedAncestors(): node.boundingBoxChanged.connect(self.propertyChanged) if event.type == Event.ToolDeactivateEvent: for node in self._getSelectedObjectsWithoutSelectedAncestors(): node.boundingBoxChanged.disconnect(self.propertyChanged) # Handle modifier keys: Shift toggles snap, Control toggles uniform scaling if event.type == Event.KeyPressEvent: if event.key == KeyEvent.ShiftKey: self.setScaleSnap(not self._snap_scale) elif event.key == KeyEvent.ControlKey: self.setNonUniformScale(not self._non_uniform_scale) if event.type == Event.KeyReleaseEvent: if event.key == KeyEvent.ShiftKey: self.setScaleSnap(not self._snap_scale) elif event.key == KeyEvent.ControlKey: self.setNonUniformScale(not self._non_uniform_scale) if event.type == Event.MousePressEvent and self._controller.getToolsEnabled( ): # Initialise a scale operation if MouseEvent.LeftButton not in event.buttons: return False id = self._selection_pass.getIdAtPosition(event.x, event.y) if not id: return False if self._handle.isAxis(id): self.setLockedAxis(id) self._saved_handle_position = self._handle.getWorldPosition() # Save the current positions of the node, as we want to scale arround their current centres self._saved_node_positions = [] for node in self._getSelectedObjectsWithoutSelectedAncestors(): self._saved_node_positions.append((node, node.getPosition())) self._scale_sum = 0.0 self._last_event = event if id == ToolHandle.XAxis: self.setDragPlane( Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.YAxis: self.setDragPlane( Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.ZAxis: self.setDragPlane( Plane(Vector(0, 1, 0), self._saved_handle_position.y)) else: self.setDragPlane( Plane(Vector(0, 1, 0), self._saved_handle_position.y)) self.setDragStart(event.x, event.y) return True if event.type == Event.MouseMoveEvent: # Perform a scale operation if not self.getDragPlane(): return False drag_position = self.getDragPosition(event.x, event.y) if drag_position: if self.getLockedAxis() == ToolHandle.XAxis: drag_position = drag_position.set(y=0, z=0) elif self.getLockedAxis() == ToolHandle.YAxis: drag_position = drag_position.set(x=0, z=0) elif self.getLockedAxis() == ToolHandle.ZAxis: drag_position = drag_position.set(x=0, y=0) drag_length = (drag_position - self._saved_handle_position).length() if self._drag_length > 0: drag_change = (drag_length - self._drag_length) / 100 * self._scale_speed if self.getLockedAxis() in [ ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis ]: # drag the handle, axis is already determined if self._snap_scale: scale_factor = round(drag_change, 1) else: scale_factor = drag_change else: # uniform scaling; because we use central cube, we use the screen x, y for scaling. # upper right is scale up, lower left is scale down scale_factor_delta = ( (self._last_event.y - event.y) - (self._last_event.x - event.x)) * self._scale_speed self._scale_sum += scale_factor_delta if self._snap_scale: scale_factor = round(self._scale_sum, 1) # remember the decimals when snap scaling self._scale_sum -= scale_factor else: scale_factor = self._scale_sum self._scale_sum = 0.0 if scale_factor: scale_change = Vector(0.0, 0.0, 0.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale_change = scale_change.set(x=scale_factor) elif self.getLockedAxis() == ToolHandle.YAxis: scale_change = scale_change.set(y=scale_factor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale_change = scale_change.set(z=scale_factor) else: # Middle handle scale_change = scale_change.set(x=scale_factor, y=scale_factor, z=scale_factor) else: scale_change = scale_change.set(x=scale_factor, y=scale_factor, z=scale_factor) # Scale around the saved centers of all selected nodes if len(self._saved_node_positions) > 1: op = GroupedOperation() for node, position in self._saved_node_positions: op.addOperation( ScaleOperation( node, scale_change, relative_scale=True, scale_around_point=position)) op.push() else: for node, position in self._saved_node_positions: ScaleOperation( node, scale_change, relative_scale=True, scale_around_point=position).push() self._drag_length = (self._saved_handle_position - drag_position).length() else: self.operationStarted.emit(self) self._drag_length = ( self._saved_handle_position - drag_position ).length() #First move, do nothing but set right length. self._last_event = event # remember for uniform drag return True if event.type == Event.MouseReleaseEvent: # Finish a scale operation if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(ToolHandle.NoAxis) self._drag_length = 0 self.operationStopped.emit(self) return True
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the # same direction. transformed_nodes = [] group_nodes = [] # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. nodes = list(BreadthFirstIterator(root)) random.shuffle(nodes) for node in nodes: if node is root or type( node) is not SceneNode or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox() if build_volume_bounding_box: # It's over 9000! build_volume_bounding_box = build_volume_bounding_box.set( bottom=-9001) else: # No bounding box. This is triggered when running Cura from command line with a model for the first time # In that situation there is a model, but no machine (and therefore no build volume. return node._outside_buildarea = False # Mark the node as outside the build volume if the bounding box test fails. if build_volume_bounding_box.intersectsBox( bbox ) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True if node.callDecoration("isGroup"): group_nodes.append(node) # Keep list of affected group_nodes # Move it downwards if bottom is above platform move_vector = Vector() if Preferences.getInstance().getValue( "physics/automatic_drop_down") and not ( node.getParent() and node.getParent().callDecoration( "isGroup")) and node.isEnabled( ): #If an object is grouped, don't move it down z_offset = node.callDecoration( "getZOffset") if node.getDecorator( ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y=-bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) if Preferences.getInstance().getValue( "physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or type( other_node) is not SceneNode or other_node is node: continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren( ) or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent() and node.getParent() and ( other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None): continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration( "getConvexHull") or not other_node.getBoundingBox( ): continue if other_node in transformed_nodes: continue # Other node is already moving, wait for next pass. overlap = (0, 0) # Start loop with no overlap current_overlap_checks = 0 # Continue to check the overlap until we no longer find one. while overlap and current_overlap_checks < self._max_overlap_checks: current_overlap_checks += 1 head_hull = node.callDecoration("getConvexHullHead") if head_hull: # One at a time intersection. overlap = head_hull.translate( move_vector.x, move_vector.z).intersectsPolygon( other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration( "getConvexHullHead") if other_head_hull: overlap = node.callDecoration( "getConvexHull").translate( move_vector.x, move_vector.z).intersectsPolygon( other_head_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: own_convex_hull = node.callDecoration( "getConvexHull") other_convex_hull = other_node.callDecoration( "getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.translate( move_vector.x, move_vector.z).intersectsPolygon( other_convex_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None convex_hull = node.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): return # Check for collisions between disallowed areas and the object for area in self._build_volume.getDisallowedAreas(): overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): transformed_nodes.append(node) op = PlatformPhysicsOperation.PlatformPhysicsOperation( node, move_vector) op.push() # Group nodes should override the _outside_buildarea property of their children. for group_node in group_nodes: for child_node in group_node.getAllChildren(): child_node._outside_buildarea = group_node._outside_buildarea
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 _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 event(self, event): super().event(event) if event.type == Event.ToolActivateEvent: self._old_scale = Selection.getSelectedObject(0).getScale() for node in Selection.getAllSelectedObjects(): node.boundingBoxChanged.connect(self.propertyChanged) if event.type == Event.ToolDeactivateEvent: for node in Selection.getAllSelectedObjects(): node.boundingBoxChanged.disconnect(self.propertyChanged) # Handle modifier keys: Shift toggles snap, Control toggles uniform scaling if event.type == Event.KeyPressEvent: if event.key == KeyEvent.ShiftKey: self._snap_scale = False self.propertyChanged.emit() elif event.key == KeyEvent.ControlKey: self._non_uniform_scale = True self.propertyChanged.emit() if event.type == Event.KeyReleaseEvent: if event.key == KeyEvent.ShiftKey: self._snap_scale = True self.propertyChanged.emit() elif event.key == KeyEvent.ControlKey: self._non_uniform_scale = False self.propertyChanged.emit() if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): # Initialise a scale operation if MouseEvent.LeftButton not in event.buttons: return False id = self._selection_pass.getIdAtPosition(event.x, event.y) if not id: return False if ToolHandle.isAxis(id): self.setLockedAxis(id) # Save the current positions of the node, as we want to scale arround their current centres self._saved_node_positions = [] for node in Selection.getAllSelectedObjects(): self._saved_node_positions.append((node, node.getWorldPosition())) self._saved_handle_position = self._handle.getWorldPosition() if id == ToolHandle.XAxis: self.setDragPlane(Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.YAxis: self.setDragPlane(Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.ZAxis: self.setDragPlane(Plane(Vector(0, 1, 0), self._saved_handle_position.y)) else: self.setDragPlane(Plane(Vector(0, 1, 0), self._saved_handle_position.y)) self.setDragStart(event.x, event.y) self.operationStarted.emit(self) if event.type == Event.MouseMoveEvent: # Perform a scale operation if not self.getDragPlane(): return False drag_position = self.getDragPosition(event.x, event.y) if drag_position: drag_length = (drag_position - self._saved_handle_position).length() if self._drag_length > 0: drag_change = (drag_length - self._drag_length) / 100 * self._scale_speed if self._snap_scale: scale_factor = round(drag_change, 1) else: scale_factor = drag_change if scale_factor: scale_change = Vector(0.0, 0.0, 0.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale_change = scale_change.set(x=scale_factor) elif self.getLockedAxis() == ToolHandle.YAxis: scale_change = scale_change.set(y=scale_factor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale_change = scale_change.set(z=scale_factor) else: scale_change = Vector(x=scale_factor, y=scale_factor, z=scale_factor) # Scale around the saved centeres of all selected nodes op = GroupedOperation() for node, position in self._saved_node_positions: op.addOperation(ScaleOperation(node, scale_change, relative_scale = True, scale_around_point = position)) op.push() self._drag_length = (self._saved_handle_position - drag_position).length() else: self._drag_length = (self._saved_handle_position - drag_position).length() #First move, do nothing but set right length. return True if event.type == Event.MouseReleaseEvent: # Finish a scale operation if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(None) self._drag_length = 0 self.operationStopped.emit(self) return True
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the # same direction. transformed_nodes = [] for node in BreadthFirstIterator(root): if node is root or type( node) is not SceneNode or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox() if build_volume_bounding_box: # It's over 9000! build_volume_bounding_box = build_volume_bounding_box.set( bottom=-9001) else: # No bounding box. This is triggered when running Cura from command line with a model for the first time # In that situation there is a model, but no machine (and therefore no build volume. return node._outside_buildarea = False # Mark the node as outside the build volume if the bounding box test fails. if build_volume_bounding_box.intersectsBox( bbox ) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True # Move it downwards if bottom is above platform move_vector = Vector() if Preferences.getInstance().getValue( "physics/automatic_drop_down") and not ( node.getParent() and node.getParent().callDecoration("isGroup") ): #If an object is grouped, don't move it down z_offset = node.callDecoration( "getZOffset") if node.getDecorator( ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y=-bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) if Preferences.getInstance().getValue( "physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or type( other_node) is not SceneNode or other_node is node: continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren( ) or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent().callDecoration( "isGroup") is not None or node.getParent( ).callDecoration("isGroup") is not None: continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration( "getConvexHull") or not other_node.getBoundingBox( ): continue if other_node in transformed_nodes: continue # Other node is already moving, wait for next pass. # Get the overlap distance for both convex hulls. If this returns None, there is no intersection. head_hull = node.callDecoration("getConvexHullHead") if head_hull: overlap = head_hull.intersectsPolygon( other_node.callDecoration("getConvexHullHead")) if not overlap: other_head_hull = other_node.callDecoration( "getConvexHullHead") if other_head_hull: overlap = node.callDecoration( "getConvexHullHead").intersectsPolygon( other_head_hull) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration( "getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.intersectsPolygon( other_convex_hull) else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None if overlap is None: continue move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1) convex_hull = node.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): return # Check for collisions between disallowed areas and the object for area in self._build_volume.getDisallowedAreas(): overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): transformed_nodes.append(node) op = PlatformPhysicsOperation.PlatformPhysicsOperation( node, move_vector) op.push()
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the # same direction. transformed_nodes = [] # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. nodes = list(BreadthFirstIterator(root)) # Only check nodes inside build area. nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)] random.shuffle(nodes) for node in nodes: if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Move it downwards if bottom is above platform move_vector = Vector() if Application.getInstance().getPreferences().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh"): node.addDecorator(ConvexHullDecorator()) # only push away objects if this node is a printing mesh if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"): # Do not move locked nodes if node.getSetting(SceneNodeSettings.LockPosition): continue # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or not issubclass(type(other_node), SceneNode) or other_node is node or other_node.callDecoration("getBuildPlateNumber") != node.callDecoration("getBuildPlateNumber"): continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren() or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent() and node.getParent() and (other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None): continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox(): continue if other_node in transformed_nodes: continue # Other node is already moving, wait for next pass. if other_node.callDecoration("isNonPrintingMesh"): continue overlap = (0, 0) # Start loop with no overlap current_overlap_checks = 0 # Continue to check the overlap until we no longer find one. while overlap and current_overlap_checks < self._max_overlap_checks: current_overlap_checks += 1 head_hull = node.callDecoration("getConvexHullHead") if head_hull: # One at a time intersection. overlap = head_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor, z = move_vector.z + overlap[1] * self._move_factor) else: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor, z = move_vector.z + overlap[1] * self._move_factor) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull) if overlap: # Moving ensured that overlap was still there. Try anew! temp_move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor, z = move_vector.z + overlap[1] * self._move_factor) # if the distance between two models less than 2mm then try to find a new factor if abs(temp_move_vector.x - overlap[0]) < self._minimum_gap and abs(temp_move_vector.y - overlap[1]) < self._minimum_gap: temp_x_factor = (abs(overlap[0]) + self._minimum_gap) / overlap[0] if overlap[0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58 temp_y_factor = (abs(overlap[1]) + self._minimum_gap) / overlap[1] if overlap[1] != 0 else 0 # find y move_factor temp_scale_factor = temp_x_factor if abs(temp_x_factor) > abs(temp_y_factor) else temp_y_factor move_vector = move_vector.set(x = move_vector.x + overlap[0] * temp_scale_factor, z = move_vector.z + overlap[1] * temp_scale_factor) else: move_vector = temp_move_vector else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None if not Vector.Null.equals(move_vector, epsilon = 1e-5): transformed_nodes.append(node) op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() # After moving, we have to evaluate the boundary checks for nodes build_volume = Application.getInstance().getBuildVolume() build_volume.updateNodeBoundaryCheck()
def event(self, event): super().event(event) if event.type == Event.ToolActivateEvent: for node in Selection.getAllSelectedObjects(): node.boundingBoxChanged.connect(self.propertyChanged) if event.type == Event.ToolDeactivateEvent: for node in Selection.getAllSelectedObjects(): node.boundingBoxChanged.disconnect(self.propertyChanged) # Handle modifier keys: Shift toggles snap, Control toggles uniform scaling if event.type == Event.KeyPressEvent: if event.key == KeyEvent.ShiftKey: self._snap_scale = False self.propertyChanged.emit() elif event.key == KeyEvent.ControlKey: self._non_uniform_scale = True self.propertyChanged.emit() if event.type == Event.KeyReleaseEvent: if event.key == KeyEvent.ShiftKey: self._snap_scale = True self.propertyChanged.emit() elif event.key == KeyEvent.ControlKey: self._non_uniform_scale = False self.propertyChanged.emit() if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): # Initialise a scale operation if MouseEvent.LeftButton not in event.buttons: return False id = self._selection_pass.getIdAtPosition(event.x, event.y) if not id: return False if self._handle.isAxis(id): self.setLockedAxis(id) self._saved_handle_position = self._handle.getWorldPosition() # Save the current positions of the node, as we want to scale arround their current centres self._saved_node_positions = [] for node in Selection.getAllSelectedObjects(): self._saved_node_positions.append((node, node.getPosition())) self._scale_sum = 0.0 self._last_event = event if id == ToolHandle.XAxis: self.setDragPlane(Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.YAxis: self.setDragPlane(Plane(Vector(0, 0, 1), self._saved_handle_position.z)) elif id == ToolHandle.ZAxis: self.setDragPlane(Plane(Vector(0, 1, 0), self._saved_handle_position.y)) else: self.setDragPlane(Plane(Vector(0, 1, 0), self._saved_handle_position.y)) self.setDragStart(event.x, event.y) if event.type == Event.MouseMoveEvent: # Perform a scale operation if not self.getDragPlane(): return False drag_position = self.getDragPosition(event.x, event.y) if drag_position: drag_length = (drag_position - self._saved_handle_position).length() if self._drag_length > 0: drag_change = (drag_length - self._drag_length) / 100 * self._scale_speed if self.getLockedAxis() in [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis]: # drag the handle, axis is already determined if self._snap_scale: scale_factor = round(drag_change, 1) else: scale_factor = drag_change else: # uniform scaling; because we use central cube, we use the screen x, y for scaling. # upper right is scale up, lower left is scale down scale_factor_delta = ((self._last_event.y - event.y) - (self._last_event.x - event.x)) * self._scale_speed self._scale_sum += scale_factor_delta if self._snap_scale: scale_factor = round(self._scale_sum, 1) # remember the decimals when snap scaling self._scale_sum -= scale_factor else: scale_factor = self._scale_sum self._scale_sum = 0.0 if scale_factor: scale_change = Vector(0.0, 0.0, 0.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale_change = scale_change.set(x=scale_factor) elif self.getLockedAxis() == ToolHandle.YAxis: scale_change = scale_change.set(y=scale_factor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale_change = scale_change.set(z=scale_factor) else: # Middle handle scale_change = scale_change.set(x=scale_factor, y=scale_factor, z=scale_factor) else: scale_change = scale_change.set(x=scale_factor, y=scale_factor, z=scale_factor) # Scale around the saved centers of all selected nodes op = GroupedOperation() for node, position in self._saved_node_positions: op.addOperation(ScaleOperation(node, scale_change, relative_scale = True, scale_around_point = position)) op.push() self._drag_length = (self._saved_handle_position - drag_position).length() else: self.operationStarted.emit(self) self._drag_length = (self._saved_handle_position - drag_position).length() #First move, do nothing but set right length. self._last_event = event # remember for uniform drag return True if event.type == Event.MouseReleaseEvent: # Finish a scale operation if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(None) self._drag_length = 0 self.operationStopped.emit(self) return True
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() for node in BreadthFirstIterator(root): if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001) node._outside_buildarea = False # Mark the node as outside the build volume if the bounding box test fails. if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True # Move it downwards if bottom is above platform move_vector = Vector() if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 if bbox.bottom > 0: move_vector = move_vector.set(y=-bbox.bottom + z_offset) elif bbox.bottom < z_offset: move_vector = move_vector.set(y=(-bbox.bottom) - z_offset) #if not Float.fuzzyCompare(bbox.bottom, 0.0): # pass#move_vector.setY(-bbox.bottom) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) node.callDecoration("recomputeConvexHull") if Preferences.getInstance().getValue("physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or type(other_node) is not SceneNode or other_node is node: continue # Ignore colissions of a group with it's own children if other_node in node.getAllChildren() or node in other_node.getAllChildren(): continue # Ignore colissions within a group if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None: continue #if node.getParent().callDecoration("isGroup") is other_node.getParent().callDecoration("isGroup"): # continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox(): continue # Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects. #if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection: # continue # Get the overlap distance for both convex hulls. If this returns None, there is no intersection. try: head_hull = node.callDecoration("getConvexHullHead") if head_hull: overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull) else: overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull")) except: overlap = None #It can sometimes occur that the calculated convex hull has no size, in which case there is no overlap. if overlap is None: continue move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1) convex_hull = node.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): return # Check for collisions between disallowed areas and the object for area in self._build_volume.getDisallowedAreas(): overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push()
def redo(self): if self._set_scale: #Simply change the scale. self._node.setScale(self._scale, SceneNode.TransformSpace.World) elif self._add_scale: #Add to the current scale. self._node.setScale(self._node.getScale() + self._scale) elif self._relative_scale: #Scale relatively to the current scale. scale_factor = Vector() ## Ensure that the direction is correctly applied (it can be flipped due to mirror) if self._scale.z == self._scale.y and self._scale.y == self._scale.x: ratio = (1 / (self._node.getScale().x + self._node.getScale().y + self._node.getScale().z)) * 3 ratio_vector = ratio * self._node.getScale() self._scale *= ratio_vector if self._node.getScale().x > 0: scale_factor = scale_factor.set(x=abs(self._node.getScale().x + self._scale.x)) else: scale_factor = scale_factor.set(x=-abs(self._node.getScale().x - self._scale.x)) if self._node.getScale().y > 0: scale_factor = scale_factor.set(y=abs(self._node.getScale().y + self._scale.y)) else: scale_factor = scale_factor.set(y=-abs(self._node.getScale().y - self._scale.y)) if self._node.getScale().z > 0: scale_factor = scale_factor.set(z=abs(self._node.getScale().z + self._scale.z)) else: scale_factor = scale_factor.set(z=-abs(self._node.getScale().z - self._scale.z)) current_scale = self._node.getScale() if scale_factor.x != 0: scale_factor = scale_factor.set(x=scale_factor.x / current_scale.x) if scale_factor.y != 0: scale_factor = scale_factor.set(y=scale_factor.y / current_scale.y) if scale_factor.z != 0: scale_factor = scale_factor.set(z=scale_factor.z / current_scale.z) self._node.setPosition(-self._scale_around_point) #If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation. self._node.scale(scale_factor, SceneNode.TransformSpace.Parent) self._node.setPosition(self._scale_around_point) new_scale = self._node.getScale() if self._snap: if scale_factor.x != 1.0: new_scale = new_scale.set(x=round(new_scale.x, 2)) if scale_factor.y != 1.0: new_scale = new_scale.set(y=round(new_scale.y, 2)) if scale_factor.z != 1.0 : new_scale = new_scale.set(z=round(new_scale.z, 2)) # Enforce min size. if new_scale.x < self._min_scale and new_scale.x >= 0: new_scale = new_scale.set(x=self._min_scale) if new_scale.y < self._min_scale and new_scale.y >= 0: new_scale = new_scale.set(y=self._min_scale) if new_scale.z < self._min_scale and new_scale.z >= 0: new_scale = new_scale.set(z=self._min_scale) # Enforce min size (when mirrored) if new_scale.x > -self._min_scale and new_scale.x <= 0: new_scale = new_scale.set(x=-self._min_scale) if new_scale.y > -self._min_scale and new_scale.y <= 0: new_scale = new_scale.set(y=-self._min_scale) if new_scale.z > -self._min_scale and new_scale.z <=0: new_scale = new_scale.set(z=-self._min_scale) self._node.setScale(new_scale, SceneNode.TransformSpace.World) else: self._node.setPosition(-self._scale_around_point, SceneNode.TransformSpace.World) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation. self._node.scale(self._scale, SceneNode.TransformSpace.World) #Default to _set_scale self._node.setPosition(self._scale_around_point, SceneNode.TransformSpace.World) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation.
def redo(self): if self._set_scale: #Simply change the scale. self._node.setScale(self._scale, SceneNode.TransformSpace.World) elif self._add_scale: #Add to the current scale. self._node.setScale(self._node.getScale() + self._scale) elif self._relative_scale: #Scale relatively to the current scale. scale_factor = Vector() ## Ensure that the direction is correctly applied (it can be flipped due to mirror) if self._scale.z == self._scale.y and self._scale.y == self._scale.x: ratio = (1 / (self._node.getScale().x + self._node.getScale().y + self._node.getScale().z)) * 3 ratio_vector = ratio * self._node.getScale() self._scale *= ratio_vector if self._node.getScale().x > 0: scale_factor = scale_factor.set(x=abs(self._node.getScale().x + self._scale.x)) else: scale_factor = scale_factor.set( x=-abs(self._node.getScale().x - self._scale.x)) if self._node.getScale().y > 0: scale_factor = scale_factor.set(y=abs(self._node.getScale().y + self._scale.y)) else: scale_factor = scale_factor.set( y=-abs(self._node.getScale().y - self._scale.y)) if self._node.getScale().z > 0: scale_factor = scale_factor.set(z=abs(self._node.getScale().z + self._scale.z)) else: scale_factor = scale_factor.set( z=-abs(self._node.getScale().z - self._scale.z)) current_scale = self._node.getScale() if scale_factor.x != 0: scale_factor = scale_factor.set(x=scale_factor.x / current_scale.x) if scale_factor.y != 0: scale_factor = scale_factor.set(y=scale_factor.y / current_scale.y) if scale_factor.z != 0: scale_factor = scale_factor.set(z=scale_factor.z / current_scale.z) self._node.setPosition( -self._scale_around_point ) #If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation. self._node.scale(scale_factor, SceneNode.TransformSpace.Parent) self._node.setPosition(self._scale_around_point) new_scale = self._node.getScale() if self._snap: if scale_factor.x != 1.0: new_scale = new_scale.set(x=round(new_scale.x, 2)) if scale_factor.y != 1.0: new_scale = new_scale.set(y=round(new_scale.y, 2)) if scale_factor.z != 1.0: new_scale = new_scale.set(z=round(new_scale.z, 2)) # Enforce min size. if new_scale.x < self._min_scale and new_scale.x >= 0: new_scale = new_scale.set(x=self._min_scale) if new_scale.y < self._min_scale and new_scale.y >= 0: new_scale = new_scale.set(y=self._min_scale) if new_scale.z < self._min_scale and new_scale.z >= 0: new_scale = new_scale.set(z=self._min_scale) # Enforce min size (when mirrored) if new_scale.x > -self._min_scale and new_scale.x <= 0: new_scale = new_scale.set(x=-self._min_scale) if new_scale.y > -self._min_scale and new_scale.y <= 0: new_scale = new_scale.set(y=-self._min_scale) if new_scale.z > -self._min_scale and new_scale.z <= 0: new_scale = new_scale.set(z=-self._min_scale) self._node.setScale(new_scale, SceneNode.TransformSpace.World) else: self._node.setPosition( -self._scale_around_point, SceneNode.TransformSpace.World ) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation. self._node.scale( self._scale, SceneNode.TransformSpace.World) #Default to _set_scale self._node.setPosition( self._scale_around_point, SceneNode.TransformSpace.World ) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation.
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the # same direction. transformed_nodes = [] # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. nodes = list(BreadthFirstIterator(root)) # Only check nodes inside build area. nodes = [ node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea) ] random.shuffle(nodes) for node in nodes: if node is root or not isinstance(node, SceneNode) or isinstance( node, DuplicatedNode) or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Move it downwards if bottom is above platform move_vector = Vector() if Preferences.getInstance().getValue( "physics/automatic_drop_down") and not ( node.getParent() and node.getParent().callDecoration( "isGroup")) and node.isEnabled( ): #If an object is grouped, don't move it down z_offset = node.callDecoration( "getZOffset") if node.getDecorator( ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y=-bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) # only push away objects if this node is a printing mesh if not node.callDecoration( "isNonPrintingMesh") and Preferences.getInstance( ).getValue("physics/automatic_push_free" ) and type(node) != DuplicatedNode: # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or not issubclass( type(other_node), SceneNode ) or other_node is node or other_node.callDecoration( "getBuildPlateNumber") != node.callDecoration( "getBuildPlateNumber"): continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren( ) or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent() and node.getParent() and ( other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None): continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration( "getConvexHull") or not other_node.getBoundingBox( ): continue if other_node in transformed_nodes: continue # Other node is already moving, wait for next pass. if other_node.callDecoration("isNonPrintingMesh"): continue overlap = (0, 0) # Start loop with no overlap current_overlap_checks = 0 # Continue to check the overlap until we no longer find one. while overlap and current_overlap_checks < self._max_overlap_checks: current_overlap_checks += 1 head_hull = node.callDecoration("getConvexHullHead") if head_hull: # One at a time intersection. overlap = head_hull.translate( move_vector.x, move_vector.z).intersectsPolygon( other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration( "getConvexHullHead") if other_head_hull: overlap = node.callDecoration( "getConvexHull").translate( move_vector.x, move_vector.z).intersectsPolygon( other_head_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: own_convex_hull = node.callDecoration( "getConvexHull") other_convex_hull = other_node.callDecoration( "getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.translate( move_vector.x, move_vector.z).intersectsPolygon( other_convex_hull) if overlap: # Moving ensured that overlap was still there. Try anew! temp_move_vector = move_vector.set( x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) # if the distance between two models less than 2mm then try to find a new factor if abs(temp_move_vector.x - overlap[0] ) < self._minimum_gap and abs( temp_move_vector.y - overlap[1]) < self._minimum_gap: temp_x_factor = ( abs(overlap[0]) + self._minimum_gap ) / overlap[0] if overlap[ 0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58 temp_y_factor = ( abs(overlap[1]) + self._minimum_gap ) / overlap[1] if overlap[ 1] != 0 else 0 # find y move_factor temp_scale_factor = temp_x_factor if abs( temp_x_factor) > abs( temp_y_factor ) else temp_y_factor move_vector = move_vector.set( x=move_vector.x + overlap[0] * temp_scale_factor, z=move_vector.z + overlap[1] * temp_scale_factor) else: move_vector = temp_move_vector else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None if not Vector.Null.equals(move_vector, epsilon=1e-5): transformed_nodes.append(node) op = PlatformPhysicsOperation.PlatformPhysicsOperation( node, move_vector) op.push() # After moving, we have to evaluate the boundary checks for nodes build_volume = Application.getInstance().getBuildVolume() build_volume.updateNodeBoundaryCheck()
def _onChangeTimerFinished(self): if not self._enabled: return root = self._controller.getScene().getRoot() # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the # same direction. transformed_nodes = [] group_nodes = [] # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. nodes = list(BreadthFirstIterator(root)) random.shuffle(nodes) for node in nodes: if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox() if build_volume_bounding_box: # It's over 9000! build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001) else: # No bounding box. This is triggered when running Cura from command line with a model for the first time # In that situation there is a model, but no machine (and therefore no build volume. return node._outside_buildarea = False # Mark the node as outside the build volume if the bounding box test fails. if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True if node.callDecoration("isGroup"): group_nodes.append(node) # Keep list of affected group_nodes # Move it downwards if bottom is above platform move_vector = Vector() if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y=-bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) if Preferences.getInstance().getValue("physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. if other_node is root or type(other_node) is not SceneNode or other_node is node: continue # Ignore collisions of a group with it's own children if other_node in node.getAllChildren() or node in other_node.getAllChildren(): continue # Ignore collisions within a group if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None: continue # Ignore nodes that do not have the right properties set. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox(): continue if other_node in transformed_nodes: continue # Other node is already moving, wait for next pass. overlap = (0, 0) # Start loop with no overlap current_overlap_checks = 0 # Continue to check the overlap until we no longer find one. while overlap and current_overlap_checks < self._max_overlap_checks: current_overlap_checks += 1 head_hull = node.callDecoration("getConvexHullHead") if head_hull: # One at a time intersection. overlap = head_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull") if own_convex_hull and other_convex_hull: overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull) if overlap: # Moving ensured that overlap was still there. Try anew! move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor, z=move_vector.z + overlap[1] * self._move_factor) else: # This can happen in some cases if the object is not yet done with being loaded. # Simply waiting for the next tick seems to resolve this correctly. overlap = None convex_hull = node.callDecoration("getConvexHull") if convex_hull: if not convex_hull.isValid(): return # Check for collisions between disallowed areas and the object for area in self._build_volume.getDisallowedAreas(): overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): transformed_nodes.append(node) op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() # Group nodes should override the _outside_buildarea property of their children. for group_node in group_nodes: for child_node in group_node.getAllChildren(): child_node._outside_buildarea = group_node._outside_buildarea