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 _onChangeTimerFinished(self): root = self._controller.getScene().getRoot() for node in BreadthFirstIterator(root): if node is root or type(node) is not SceneNode: continue bbox = node.getBoundingBox() if not bbox or not bbox.isValid(): continue # Mark the node as outside the build volume if the bounding box test fails. if self._build_volume.getBoundingBox().intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True else: node._outside_buildarea = False # Move the node upwards if the bottom is below the build platform. move_vector = Vector() if not Float.fuzzyCompare(bbox.bottom, 0.0): move_vector.setY(-bbox.bottom) # If there is no convex hull for the node, start calculating it and continue. if not hasattr(node, "_convex_hull"): if not hasattr(node, "_convex_hull_job"): job = ConvexHullJob.ConvexHullJob(node) job.start() node._convex_hull_job = job elif Selection.isSelected(node): pass else: # 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 nodes that do not have the right properties set. if not hasattr(other_node, "_convex_hull") 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. overlap = node._convex_hull.intersectsPolygon(other_node._convex_hull) if overlap is None: continue move_vector.setX(overlap[0] * 1.1) move_vector.setZ(overlap[1] * 1.1) if move_vector != Vector(): op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() if node.getBoundingBox().intersectsBox(self._build_volume.getBoundingBox()) == AxisAlignedBox.IntersectionResult.FullIntersection: op = ScaleToBoundsOperation(node, self._build_volume.getBoundingBox()) op.push()
def event(self, event): super().event(event) if event.type == Event.MousePressEvent and self._controller.getToolsEnabled( ): # Initialise a mirror 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) return True if event.type == Event.MouseReleaseEvent: # Perform a mirror operation if self.getLockedAxis(): op = None if Selection.getCount() == 1: node = Selection.getSelectedObject(0) mirror = Vector(1, 1, 1) if self.getLockedAxis() == ToolHandle.XAxis: mirror.setX(-1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror.setY(-1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror.setZ(-1) op = MirrorOperation(node, mirror, mirror_around_center=True) else: op = GroupedOperation() for node in Selection.getAllSelectedObjects(): mirror = Vector(1, 1, 1) if self.getLockedAxis() == ToolHandle.XAxis: mirror.setX(-1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror.setY(-1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror.setZ(-1) op.addOperation( MirrorOperation(node, mirror, mirror_around_center=True)) op.push() self.setLockedAxis(None) return True return False
def event(self, event): super().event(event) if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): 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) return True if event.type == Event.MouseReleaseEvent: if self.getLockedAxis(): op = None if Selection.getCount() == 1: node = Selection.getSelectedObject(0) mirror = Vector(1,1,1) if self.getLockedAxis() == ToolHandle.XAxis: mirror.setX(-1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror.setY(-1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror.setZ(-1) op = MirrorOperation(node, mirror, mirror_around_center = True) else: op = GroupedOperation() for node in Selection.getAllSelectedObjects(): mirror = node.getMirror() if self.getLockedAxis() == ToolHandle.XAxis: mirror.setX(-1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror.setY(-1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror.setZ(-1) op.addOperation(MirrorOperation(node, mirror, mirror_around_center = True)) op.push() self.setLockedAxis(None) return True return False
def __iadd__(self, other): if not other.isValid(): return self newMin = Vector() newMin.setX(min(self._min.x, other.left)) newMin.setY(min(self._min.y, other.bottom)) newMin.setZ(min(self._min.z, other.back)) newMax = Vector() newMax.setX(max(self._max.x, other.right)) newMax.setY(max(self._max.y, other.top)) newMax.setZ(max(self._max.z, other.front)) self._min = newMin self._max = newMax return self
def __iadd__(self, other): if not other.isValid(): return self new_min = Vector() new_min.setX(min(self._min.x, other.left)) new_min.setY(min(self._min.y, other.bottom)) new_min.setZ(min(self._min.z, other.back)) new_max = Vector() new_max.setX(max(self._max.x, other.right)) new_max.setY(max(self._max.y, other.top)) new_max.setZ(max(self._max.z, other.front)) self._min = new_min self._max = new_max return self
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 redo(self): if self._set_scale: self._node.setScale(self._scale) elif self._add_scale: self._node.setScale(self._node.getScale() + self._scale) elif self._relative_scale: scale_factor = Vector() ## Ensure that the direction is correctly applied (it can be flipped due to mirror) if self._node.getScale().x > 0: scale_factor.setX(self._node.getScale().x + self._scale.x) else: scale_factor.setX(self._node.getScale().x - self._scale.x) if self._node.getScale().y > 0: scale_factor.setY(self._node.getScale().y + self._scale.y) else: scale_factor.setY(self._node.getScale().y - self._scale.y) if self._node.getScale().z > 0: scale_factor.setZ(self._node.getScale().z + self._scale.z) else: scale_factor.setZ(self._node.getScale().z - self._scale.z) current_scale = copy.deepcopy(self._node.getScale()) scale_factor.setX(scale_factor.x / current_scale.x) scale_factor.setY(scale_factor.y / current_scale.y) scale_factor.setZ(scale_factor.z / current_scale.z) self._node.scale(scale_factor, SceneNode.TransformSpace.Parent) new_scale = copy.deepcopy(self._node.getScale()) if self._snap: if(scale_factor.x != 1.0): new_scale.setX(round(new_scale.x, 1)) if(scale_factor.y != 1.0): new_scale.setY(round(new_scale.y, 1)) if(scale_factor.z != 1.0): new_scale.setZ(round(new_scale.z, 1)) # Enforce min size. if new_scale.x < self._min_scale and new_scale.x >=0: new_scale.setX(self._min_scale) if new_scale.y < self._min_scale and new_scale.y >=0: new_scale.setY(self._min_scale) if new_scale.z < self._min_scale and new_scale.z >=0: new_scale.setZ(self._min_scale) # Enforce min size (when mirrored) if new_scale.x > -self._min_scale and new_scale.x <=0: new_scale.setX(-self._min_scale) if new_scale.y > -self._min_scale and new_scale.y <=0: new_scale.setY(-self._min_scale) if new_scale.z > -self._min_scale and new_scale.z <=0: new_scale.setZ(-self._min_scale) self._node.setScale(new_scale, SceneNode.TransformSpace.World) else: self._node.scale(self._scale, SceneNode.TransformSpace.World)
def redo(self): if self._set_scale: self._node.setScale(self._scale) elif self._add_scale: self._node.setScale(self._node.getScale() + self._scale) elif self._relative_scale: scale_factor = Vector() ## Ensure that the direction is correctly applied (it can be flipped due to mirror) if self._node.getScale().x > 0: scale_factor.setX(self._node.getScale().x + self._scale.x) else: scale_factor.setX(self._node.getScale().x - self._scale.x) if self._node.getScale().y > 0: scale_factor.setY(self._node.getScale().y + self._scale.y) else: scale_factor.setY(self._node.getScale().y - self._scale.y) if self._node.getScale().z > 0: scale_factor.setZ(self._node.getScale().z + self._scale.z) else: scale_factor.setZ(self._node.getScale().z - self._scale.z) current_scale = copy.deepcopy(self._node.getScale()) scale_factor.setX(scale_factor.x / current_scale.x) scale_factor.setY(scale_factor.y / current_scale.y) scale_factor.setZ(scale_factor.z / current_scale.z) self._node.scale(scale_factor, SceneNode.TransformSpace.Parent) new_scale = copy.deepcopy(self._node.getScale()) if self._snap: if (scale_factor.x != 1.0): new_scale.setX(round(new_scale.x, 1)) if (scale_factor.y != 1.0): new_scale.setY(round(new_scale.y, 1)) if (scale_factor.z != 1.0): new_scale.setZ(round(new_scale.z, 1)) # Enforce min size. if new_scale.x < self._min_scale and new_scale.x >= 0: new_scale.setX(self._min_scale) if new_scale.y < self._min_scale and new_scale.y >= 0: new_scale.setY(self._min_scale) if new_scale.z < self._min_scale and new_scale.z >= 0: new_scale.setZ(self._min_scale) # Enforce min size (when mirrored) if new_scale.x > -self._min_scale and new_scale.x <= 0: new_scale.setX(-self._min_scale) if new_scale.y > -self._min_scale and new_scale.y <= 0: new_scale.setY(-self._min_scale) if new_scale.z > -self._min_scale and new_scale.z <= 0: new_scale.setZ(-self._min_scale) self._node.setScale(new_scale, SceneNode.TransformSpace.World) else: self._node.scale(self._scale, SceneNode.TransformSpace.World)
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: continue bbox = node.getBoundingBox() if not bbox or not bbox.isValid(): self._change_timer.start() continue build_volume_bounding_box = copy.deepcopy(self._build_volume.getBoundingBox()) build_volume_bounding_box.setBottom(-9001) # Ignore intersections with the bottom # 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 else: node._outside_buildarea = False # 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 if bbox.bottom > 0: move_vector.setY(-bbox.bottom) #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()) if not node.callDecoration("getConvexHull"): if not node.callDecoration("getConvexHullJob"): job = ConvexHullJob.ConvexHullJob(node) job.start() node.callDecoration("setConvexHullJob", job) elif Selection.isSelected(node): pass elif 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 caclulated convex hull has no size, in which case there is no overlap. if overlap is None: continue move_vector.setX(overlap[0] * 1.01) move_vector.setZ(overlap[1] * 1.01) 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 move_vector != Vector(): op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push()
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert): mesh = None scene_node = None scene_node = SceneNode() mesh = MeshData() scene_node.setMeshData(mesh) 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.setZ(scale_vector.z * aspect) elif height > width: scale_vector.setX(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 i 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.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFace(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.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFace(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.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) 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) 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(): 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) 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: if not self.getDragPlane(): return False #handle_position = self._handle.getWorldPosition() 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 scale_factor = drag_change scale_change = Vector(0.0, 0.0, 0.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale_change.setX(scale_factor) elif self.getLockedAxis() == ToolHandle.YAxis: scale_change.setY(scale_factor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale_change.setZ(scale_factor) else: scale_change.setX(scale_factor) scale_change.setY(scale_factor) scale_change.setZ(scale_factor) Selection.applyOperation(ScaleOperation, scale_change, relative_scale = True, snap = self._snap_scale) self._drag_length = (self._saved_handle_position - drag_position).length() return True if event.type == Event.MouseReleaseEvent: if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(None) self._drag_length = 0 self.operationStopped.emit(self) return True
class AxisAlignedBox: class IntersectionResult: NoIntersection = 1 PartialIntersection = 2 FullIntersection = 3 def __init__(self, *args, **kwargs): super().__init__() self._min = Vector() self._max = Vector() if len(args) == 3: self.setLeft(-args[0] / 2) self.setRight(args[0] / 2) self.setTop(args[1] / 2) self.setBottom(-args[1] / 2) self.setBack(-args[2] / 2) self.setFront(args[2] / 2) if "minimum" in kwargs: self._min = kwargs["minimum"] if "maximum" in kwargs: self._max = kwargs["maximum"] ## Sometimes the min and max are not correctly set. if not self._max: self._max = Vector() if not self._min: self._min = Vector() self._ensureMinMax() def __add__(self, other): b = AxisAlignedBox() b += self b += other return b def __iadd__(self, other): if not other.isValid(): return self newMin = Vector() newMin.setX(min(self._min.x, other.left)) newMin.setY(min(self._min.y, other.bottom)) newMin.setZ(min(self._min.z, other.back)) newMax = Vector() newMax.setX(max(self._max.x, other.right)) newMax.setY(max(self._max.y, other.top)) newMax.setZ(max(self._max.z, other.front)) self._min = newMin self._max = newMax return self #def __sub__(self, other): #b = AxisAlignedBox() #b += self #b -= other #return b #def __isub__(self, other): #self._dimensions -= other._dimensions #self._center = self._dimensions / 2.0 #return self @property def width(self): return self._max.x - self._min.x @property def height(self): return self._max.y - self._min.y @property def depth(self): return self._max.z - self._min.z @property def center(self): return self._min + ((self._max - self._min) / 2.0) @property def left(self): return self._min.x def setLeft(self, value): self._min.setX(value) self._ensureMinMax() @property def right(self): return self._max.x def setRight(self, value): self._max.setX(value) self._ensureMinMax() @property def bottom(self): return self._min.y def setBottom(self, value): self._min.setY(value) self._ensureMinMax() @property def top(self): return self._max.y def setTop(self, value): self._max.setY(value) self._ensureMinMax() @property def back(self): return self._min.z def setBack(self, value): self._min.setZ(value) self._ensureMinMax() @property def front(self): return self._max.z def setFront(self, value): self._max.setZ(value) self._ensureMinMax() @property def minimum(self): return self._min def setMinimum(self, m): self._min = m self._ensureMinMax() @property def maximum(self): return self._max def setMaximum(self, m): self._max = m self._ensureMinMax() ## Check if the bounding box is valid. # Uses fuzzycompare to validate. # \sa Float::fuzzyCompare() def isValid(self): return not(Float.fuzzyCompare(self._min.x, self._max.x) or Float.fuzzyCompare(self._min.y, self._max.y) or Float.fuzzyCompare(self._min.z, self._max.z)) ## Intersect the bounding box with a ray # \param ray \type{Ray} # \sa Ray def intersectsRay(self, ray): inv = ray.inverseDirection t = numpy.empty((2,3), dtype=numpy.float32) t[0, 0] = inv.x * (self._min.x - ray.origin.x) t[0, 1] = inv.y * (self._min.y - ray.origin.y) t[0, 2] = inv.z * (self._min.z - ray.origin.z) t[1, 0] = inv.x * (self._max.x - ray.origin.x) t[1, 1] = inv.y * (self._max.y - ray.origin.y) t[1, 2] = inv.z * (self._max.z - ray.origin.z) tmin = numpy.min(t, axis=0) tmax = numpy.max(t, axis=0) largest_min = numpy.max(tmin) smallest_max = numpy.min(tmax) if smallest_max > largest_min: return (largest_min, smallest_max) else: return False ## Check to see if this box intersects another box. # # \param box \type{AxisAlignedBox} The box to check for intersection. # \return \type{IntersectionResult} NoIntersection when no intersection occurs, PartialIntersection when partially intersected, FullIntersection when box is fully contained inside this box. def intersectsBox(self, box): if self._min.x > box._max.x or box._min.x > self._max.x: return self.IntersectionResult.NoIntersection if self._min.y > box._max.y or box._min.y > self._max.y: return self.IntersectionResult.NoIntersection if self._min.z > box._max.z or box._min.z > self._max.z: return self.IntersectionResult.NoIntersection if box._min >= self._min and box._max <= self._max: return self.IntersectionResult.FullIntersection return self.IntersectionResult.PartialIntersection ## private: # Ensure min contains the minimum values and max contains the maximum values def _ensureMinMax(self): if self._max.x < self._min.x: x = self._min.x self._min.setX(self._max.x) self._max.setX(x) if self._max.y < self._min.y: y = self._min.y self._min.setY(self._max.y) self._max.setY(y) if self._max.z < self._min.z: z = self._min.z self._min.setZ(self._max.z) self._max.setZ(z) def __repr__(self): return "AxisAlignedBox(min = {0}, max = {1})".format(self._min, self._max)
class AxisAlignedBox: class IntersectionResult: NoIntersection = 1 PartialIntersection = 2 FullIntersection = 3 def __init__(self, *args, **kwargs): super().__init__() self._min = Vector() self._max = Vector() if len(args) == 3: self.setLeft(-args[0] / 2) self.setRight(args[0] / 2) self.setTop(args[1] / 2) self.setBottom(-args[1] / 2) self.setBack(-args[2] / 2) self.setFront(args[2] / 2) if "minimum" in kwargs: self._min = kwargs["minimum"] if "maximum" in kwargs: self._max = kwargs["maximum"] ## Sometimes the min and max are not correctly set. if not self._max: self._max = Vector() if not self._min: self._min = Vector() self._ensureMinMax() def __add__(self, other): b = AxisAlignedBox() b += self b += other return b def __iadd__(self, other): if not other.isValid(): return self new_min = Vector() new_min.setX(min(self._min.x, other.left)) new_min.setY(min(self._min.y, other.bottom)) new_min.setZ(min(self._min.z, other.back)) new_max = Vector() new_max.setX(max(self._max.x, other.right)) new_max.setY(max(self._max.y, other.top)) new_max.setZ(max(self._max.z, other.front)) self._min = new_min self._max = new_max return self #def __sub__(self, other): #b = AxisAlignedBox() #b += self #b -= other #return b #def __isub__(self, other): #self._dimensions -= other._dimensions #self._center = self._dimensions / 2.0 #return self @property def width(self): return self._max.x - self._min.x @property def height(self): return self._max.y - self._min.y @property def depth(self): return self._max.z - self._min.z @property def center(self): return self._min + ((self._max - self._min) / 2.0) @property def left(self): return self._min.x def setLeft(self, value): self._min.setX(value) self._ensureMinMax() @property def right(self): return self._max.x def setRight(self, value): self._max.setX(value) self._ensureMinMax() @property def bottom(self): return self._min.y def setBottom(self, value): self._min.setY(value) self._ensureMinMax() @property def top(self): return self._max.y def setTop(self, value): self._max.setY(value) self._ensureMinMax() @property def back(self): return self._min.z def setBack(self, value): self._min.setZ(value) self._ensureMinMax() @property def front(self): return self._max.z def setFront(self, value): self._max.setZ(value) self._ensureMinMax() @property def minimum(self): return self._min def setMinimum(self, m): self._min = m self._ensureMinMax() @property def maximum(self): return self._max def setMaximum(self, m): self._max = m self._ensureMinMax() ## Check if the bounding box is valid. # Uses fuzzycompare to validate. # \sa Float::fuzzyCompare() def isValid(self): return not(Float.fuzzyCompare(self._min.x, self._max.x) or Float.fuzzyCompare(self._min.y, self._max.y) or Float.fuzzyCompare(self._min.z, self._max.z)) ## Intersect the bounding box with a ray # \param ray \type{Ray} # \sa Ray def intersectsRay(self, ray): inv = ray.inverseDirection t = numpy.empty((2,3), dtype=numpy.float32) t[0, 0] = inv.x * (self._min.x - ray.origin.x) t[0, 1] = inv.y * (self._min.y - ray.origin.y) t[0, 2] = inv.z * (self._min.z - ray.origin.z) t[1, 0] = inv.x * (self._max.x - ray.origin.x) t[1, 1] = inv.y * (self._max.y - ray.origin.y) t[1, 2] = inv.z * (self._max.z - ray.origin.z) tmin = numpy.min(t, axis=0) tmax = numpy.max(t, axis=0) largest_min = numpy.max(tmin) smallest_max = numpy.min(tmax) if smallest_max > largest_min: return (largest_min, smallest_max) else: return False ## Check to see if this box intersects another box. # # \param box \type{AxisAlignedBox} The box to check for intersection. # \return \type{IntersectionResult} NoIntersection when no intersection occurs, PartialIntersection when partially intersected, FullIntersection when box is fully contained inside this box. def intersectsBox(self, box): if self._min.x > box._max.x or box._min.x > self._max.x: return self.IntersectionResult.NoIntersection if self._min.y > box._max.y or box._min.y > self._max.y: return self.IntersectionResult.NoIntersection if self._min.z > box._max.z or box._min.z > self._max.z: return self.IntersectionResult.NoIntersection if box._min >= self._min and box._max <= self._max: return self.IntersectionResult.FullIntersection return self.IntersectionResult.PartialIntersection ## private: # Ensure min contains the minimum values and max contains the maximum values def _ensureMinMax(self): if self._max.x < self._min.x: x = self._min.x self._min.setX(self._max.x) self._max.setX(x) if self._max.y < self._min.y: y = self._min.y self._min.setY(self._max.y) self._max.setY(y) if self._max.z < self._min.z: z = self._min.z self._min.setZ(self._max.z) self._max.setZ(z) def __repr__(self): return "AxisAlignedBox(min = {0}, max = {1})".format(self._min, self._max)
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) if event.type == Event.KeyPressEvent: if event.key == KeyEvent.ShiftKey: self._lock_steps = 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._lock_steps = True self.propertyChanged.emit() elif event.key == KeyEvent.ControlKey: self._non_uniform_scale = False self.propertyChanged.emit() if event.type == Event.MousePressEvent: if MouseEvent.LeftButton not in event.buttons: return False id = self._renderer.getIdAtCoordinate(event.x, event.y) if not id: return False if ToolHandle.isAxis(id): self.setLockedAxis(id) handle_position = self._handle.getWorldPosition() if id == ToolHandle.XAxis: self.setDragPlane(Plane(Vector(0, 0, 1), handle_position.z)) elif id == ToolHandle.YAxis: self.setDragPlane(Plane(Vector(0, 0, 1), handle_position.z)) elif id == ToolHandle.ZAxis: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) else: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) self.setDragStart(event.x, event.y) self.operationStarted.emit(self) if event.type == Event.MouseMoveEvent: if not self.getDragPlane(): return False handle_position = self._handle.getWorldPosition() drag_position = self.getDragPosition(event.x, event.y) if drag_position: drag_length = (drag_position - handle_position).length() if self._drag_length > 0: drag_change = (drag_length - self._drag_length) / 100 if self._snap_scale and abs(drag_change) < self._snap_amount: return False scale = Vector(1.0, 1.0, 1.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale.setX(1.0 + drag_change) elif self.getLockedAxis() == ToolHandle.YAxis: scale.setY(1.0 + drag_change) elif self.getLockedAxis() == ToolHandle.ZAxis: scale.setZ(1.0 + drag_change) if scale == Vector(1.0, 1.0, 1.0): scale.setX(1.0 + drag_change) scale.setY(1.0 + drag_change) scale.setZ(1.0 + drag_change) Selection.applyOperation(ScaleOperation, scale) self._drag_length = (handle_position - drag_position).length() return True if event.type == Event.MouseReleaseEvent: 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: continue bbox = node.getBoundingBox() if not bbox or not bbox.isValid(): self._change_timer.start() continue # Mark the node as outside the build volume if the bounding box test fails. if self._build_volume.getBoundingBox().intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True else: node._outside_buildarea = False # Move the node upwards if the bottom is below the build platform. move_vector = Vector() if not Float.fuzzyCompare(bbox.bottom, 0.0): 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()) if not node.callDecoration("getConvexHull"): if not node.callDecoration("getConvexHullJob"): job = ConvexHullJob.ConvexHullJob(node) job.start() node.callDecoration("setConvexHullJob", job) elif Selection.isSelected(node): pass else: # 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: 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. overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull")) if overlap is None: continue move_vector.setX(overlap[0] * 1.1) move_vector.setZ(overlap[1] * 1.1) convex_hull = node.callDecoration("getConvexHull") if convex_hull: # 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 move_vector != Vector(): op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push()
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) 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: 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) handle_position = self._handle.getWorldPosition() if id == ToolHandle.XAxis: self.setDragPlane(Plane(Vector(0, 0, 1), handle_position.z)) elif id == ToolHandle.YAxis: self.setDragPlane(Plane(Vector(0, 0, 1), handle_position.z)) elif id == ToolHandle.ZAxis: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) else: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) self.setDragStart(event.x, event.y) self.operationStarted.emit(self) if event.type == Event.MouseMoveEvent: if not self.getDragPlane(): return False handle_position = self._handle.getWorldPosition() drag_position = self.getDragPosition(event.x, event.y) if drag_position: drag_length = (drag_position - handle_position).length() if self._drag_length > 0: drag_change = (drag_length - self._drag_length) / 100 * self._scale_speed if self._snap_scale: scaleFactor = round(drag_change, 1) else: scaleFactor = drag_change scale = Vector(0.0, 0.0, 0.0) if self._non_uniform_scale: if self.getLockedAxis() == ToolHandle.XAxis: scale.setX(scaleFactor) elif self.getLockedAxis() == ToolHandle.YAxis: scale.setY(scaleFactor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale.setZ(scaleFactor) else: scale.setX(scaleFactor) scale.setY(scaleFactor) scale.setZ(scaleFactor) Selection.applyOperation(ScaleOperation, scale, add_scale=True) #this part prevents the mesh being scaled to a size < 0. #This cannot be done before the operation (even though that would be more efficient) #because then the operation can distract more of the mesh then is remaining of its size realWorldMeshScale = Selection.getSelectedObject(0).getScale() if realWorldMeshScale.x <= 0 or realWorldMeshScale.y <= 0 or realWorldMeshScale.z <= 0: minimumScale = 0.01 #1% so the mesh never completely disapears for the user if self._snap_scale == True: minimumScale = 0.1 #10% same reason as above if realWorldMeshScale.x <= 0: realWorldMeshScale.setX(minimumScale) if realWorldMeshScale.y <= 0: realWorldMeshScale.setY(minimumScale) if realWorldMeshScale.z <= 0: realWorldMeshScale.setZ(minimumScale) Selection.applyOperation(SetTransformOperation, None, None, realWorldMeshScale) self._drag_length = (handle_position - drag_position).length() return True if event.type == Event.MouseReleaseEvent: if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(None) self._drag_length = 0 self.operationStopped.emit(self) return True
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert): mesh = None scene_node = None scene_node = SceneNode() mesh = MeshData() scene_node.setMeshData(mesh) 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.setZ(scale_vector.z * aspect) elif height > width: scale_vector.setX(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 i 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.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFace(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.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFace(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.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) 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.setX(scale_factor) elif self.getLockedAxis() == ToolHandle.YAxis: scale_change.setY(scale_factor) elif self.getLockedAxis() == ToolHandle.ZAxis: scale_change.setZ(scale_factor) else: scale_change.setX(scale_factor) scale_change.setY(scale_factor) scale_change.setZ(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() for node in BreadthFirstIterator(root): if node is root or type(node) is not SceneNode: continue bbox = node.getBoundingBox() if not bbox or not bbox.isValid(): self._change_timer.start() continue build_volume_bounding_box = copy.deepcopy( self._build_volume.getBoundingBox()) build_volume_bounding_box.setBottom( -9001) # Ignore intersections with the bottom 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.setY(-bbox.bottom + z_offset) elif bbox.bottom < z_offset: move_vector.setY((-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()) if not node.callDecoration("getConvexHull"): if not node.callDecoration("getConvexHullJob"): job = ConvexHullJob.ConvexHullJob(node) job.start() node.callDecoration("setConvexHullJob", job) elif 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("getConvexHullHead")) if not overlap: other_head_hull = other_node.callDecoration( "getConvexHullHead") if other_head_hull: overlap = node.callDecoration( "getConvexHullHead").intersectsPolygon( other_head_hull) else: overlap = node.callDecoration( "getConvexHull").intersectsPolygon( other_node.callDecoration("getConvexHull")) except: overlap = None #It can sometimes occur that the caclulated convex hull has no size, in which case there is no overlap. if overlap is None: continue move_vector.setX(overlap[0] * 1.1) move_vector.setZ(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 move_vector != Vector(): 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 * copy.deepcopy(self._node.getScale()) self._scale *= ratio_vector if self._node.getScale().x > 0: scale_factor.setX(abs(self._node.getScale().x + self._scale.x)) else: scale_factor.setX(-abs(self._node.getScale().x - self._scale.x)) if self._node.getScale().y > 0: scale_factor.setY(abs(self._node.getScale().y + self._scale.y)) else: scale_factor.setY(-abs(self._node.getScale().y - self._scale.y)) if self._node.getScale().z > 0: scale_factor.setZ(abs(self._node.getScale().z + self._scale.z)) else: scale_factor.setZ(-abs(self._node.getScale().z - self._scale.z)) current_scale = copy.deepcopy(self._node.getScale()) if scale_factor.x != 0: scale_factor.setX(scale_factor.x / current_scale.x) if scale_factor.y != 0: scale_factor.setY(scale_factor.y / current_scale.y) if scale_factor.z != 0: scale_factor.setZ(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 = copy.deepcopy(self._node.getScale()) if self._snap: if(scale_factor.x != 1.0): new_scale.setX(round(new_scale.x, 2)) if(scale_factor.y != 1.0): new_scale.setY(round(new_scale.y, 2)) if(scale_factor.z != 1.0): new_scale.setZ(round(new_scale.z, 2)) # Enforce min size. if new_scale.x < self._min_scale and new_scale.x >= 0: new_scale.setX(self._min_scale) if new_scale.y < self._min_scale and new_scale.y >= 0: new_scale.setY(self._min_scale) if new_scale.z < self._min_scale and new_scale.z >= 0: new_scale.setZ(self._min_scale) # Enforce min size (when mirrored) if new_scale.x > -self._min_scale and new_scale.x <= 0: new_scale.setX(-self._min_scale) if new_scale.y > -self._min_scale and new_scale.y <= 0: new_scale.setY(-self._min_scale) if new_scale.z > -self._min_scale and new_scale.z <=0: new_scale.setZ(-self._min_scale) self._node.setScale(new_scale, SceneNode.TransformSpace.World) else: self._node.scale(self._scale, SceneNode.TransformSpace.World) #Default to _set_scale