def _projection(self, widget=None): models = self._get_projectable_models() if not models: return progress = self.core.get("progress") progress.update(text="Calculating 2D projection") progress.set_multiple(len(models), "Model") for model_dict in models: model = model_dict.model for objname, z_level in ( ("ProjectionModelTop", model.maxz), ("ProjectionModelMiddle", (model.minz + model.maxz) / 2.0), ("ProjectionModelBottom", model.minz), ("ProjectionModelCustom", self.gui.get_object("ProjectionZLevel").get_value())): if self.gui.get_object(objname).get_active(): plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1)) self.log.info("Projecting 3D model at level z=%g" % plane.p.z) new_model = model.get_waterline_contour( plane, callback=progress.update) if new_model: self.core.get("models").add_model( new_model, name_template="Projected model #%d") else: self.log.warn("The 2D projection at z=%g is empty. Aborted." % \ plane.p.z) break progress.update_multiple() progress.finish()
class _BridgeCorner(object): # currently we only use the xy plane up_vector = Vector(0, 0, 1) def __init__(self, barycenter, location, p1, p2, p3): self.location = location self.position = p2 self.direction = pycam.Geometry.get_bisector(p1, p2, p3, self.up_vector).normalized() preferred_direction = p2.sub(barycenter).normalized() # direction_factor: 0..1 (bigger -> better) direction_factor = (preferred_direction.dot(self.direction) + 1) / 2 angle = pycam.Geometry.get_angle_pi(p1, p2, p3, self.up_vector, pi_factor=True) # angle_factor: 0..1 (bigger -> better) if angle > 0.5: # use only angles > 90 degree angle_factor = angle / 2.0 else: angle_factor = 0 # priority: 0..1 (bigger -> better) self.priority = angle_factor * direction_factor def get_position_priority(self, other_location, average_distance): return self.priority / (1 + self.get_distance(other_location) / \ average_distance) def get_distance(self, other_location): return min(abs(other_location - self.location), abs(1 + other_location - self.location)) def __str__(self): return "%s (%s) - %s" % (self.position, self.location, self.priority)
def __init__(self, point, normal=None): super(Plane, self).__init__() if normal is None: normal = Vector(0, 0, 1) self.p = point self.n = normal if not isinstance(self.n, Vector): self.n = self.n.get_vector()
def get_support_distributed(model, z_plane, average_distance, min_bridges_per_polygon, thickness, height, length, bounds, start_at_corners=False): if (average_distance == 0) or (length == 0) or (thickness == 0) \ or (height == 0): return result = Model() if not hasattr(model, "get_polygons"): model = model.get_waterline_contour( Plane(Point(0, 0, max(model.minz, z_plane)), Vector(0, 0, 1))) if model: model = model.get_flat_projection( Plane(Point(0, 0, z_plane), Vector(0, 0, 1))) if model: model = model.get_cropped_model_by_bounds(bounds) if model: polygons = model.get_polygons() else: return None # minimum required distance between two bridge start points avoid_distance = 1.5 * (abs(length) + thickness) if start_at_corners: bridge_calculator = _get_corner_bridges else: bridge_calculator = _get_edge_bridges for polygon in polygons: # no grid for _small_ inner polygons # TODO: calculate a reasonable factor (see below) if polygon.is_closed and (not polygon.is_outer()) \ and (abs(polygon.get_area()) < 25000 * thickness ** 2): continue bridges = bridge_calculator(polygon, z_plane, min_bridges_per_polygon, average_distance, avoid_distance) for pos, direction in bridges: _add_cuboid_to_model(result, pos, direction.mul(length), height, thickness) return result
def get_lines_layer(lines, z, last_z=None, step_width=None, milling_style=MILLING_STYLE_CONVENTIONAL): get_proj_point = lambda proj_point: Point(proj_point.x, proj_point.y, z) projected_lines = [] for line in lines: if (not last_z is None) and (last_z < line.minz): # the line was processed before continue elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1.z) / (line.p2.z - line.p1.z) plane_point = line.p1.add(line.vector.mul(factor)) if line.p1.z < z: p1 = get_proj_point(line.p1) p2 = line.p2 else: p1 = line.p1 p2 = get_proj_point(line.p2) projected_lines.append(Line(p1, plane_point)) yield Line(plane_point, p2) elif line.minz < last_z < line.maxz: plane = Plane(Point(0, 0, last_z), Vector(0, 0, 1)) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1.z > last_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp projected_lines.append(Line(p1, p2)) else: if line.maxz <= z: # the line is completely below z projected_lines.append(Line(get_proj_point(line.p1), get_proj_point(line.p2))) elif line.minz >= z: projected_lines.append(line) else: log.warn("Unexpected condition 'get_lines_layer': " + \ "%s / %s / %s / %s" % (line.p1, line.p2, z, last_z)) # process all projected lines for line in projected_lines: points = [] if step_width is None: points.append(line.p1) points.append(line.p2) else: if isiterable(step_width): steps = step_width else: steps = floatrange(0.0, line.len, inc=step_width) for step in steps: next_point = line.p1.add(line.dir.mul(step)) points.append(next_point) yield points
def GenerateToolPathLinePush(self, pa, line, z, previous_z, draw_callback=None): if previous_z <= line.minz: # the line is completely above the previous level pass elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1.z) / (line.p2.z - line.p1.z) plane_point = line.p1.add(line.vector.mul(factor)) self.GenerateToolPathLinePush(pa, Line(line.p1, plane_point), z, previous_z, draw_callback=draw_callback) self.GenerateToolPathLinePush(pa, Line(plane_point, line.p2), z, previous_z, draw_callback=draw_callback) elif line.minz < previous_z < line.maxz: plane = Plane(Point(0, 0, previous_z), Vector(0, 0, 1)) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1.z > previous_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp self.GenerateToolPathLinePush(pa, Line(p1, p2), z, previous_z, draw_callback=draw_callback) else: if line.maxz <= z: # the line is completely below z p1 = Point(line.p1.x, line.p1.y, z) p2 = Point(line.p2.x, line.p2.y, z) elif line.minz >= z: p1 = line.p1 p2 = line.p2 else: log.warn("Unexpected condition EC_GTPLP: %s / %s / %s / %s" % \ (line.p1, line.p2, z, previous_z)) return # no model -> no possible obstacles # model is completely below z (e.g. support bridges) -> no obstacles relevant_models = [m for m in self.models if m.maxz >= z] if not relevant_models: points = [p1, p2] elif self.physics: points = get_free_paths_ode(self.physics, p1, p2) else: points = get_free_paths_triangles(relevant_models, self.cutter, p1, p2) if points: for point in points: pa.append(point) if draw_callback: draw_callback(tool_position=points[-1], toolpath=pa.paths)
def __init__(self, plane=None): super(ContourModel, self).__init__() self.name = "contourmodel%d" % self.id if plane is None: # the default plane points upwards along the z axis plane = Plane(Point(0, 0, 0), Vector(0, 0, 1)) self._plane = plane self._line_groups = [] self._item_groups.append(self._line_groups) # there is always just one plane self._plane_groups = [self._plane] self._item_groups.append(self._plane_groups) self._cached_offset_models = {} self._export_function = \ pycam.Exporters.SVGExporter.SVGExporterContourModel
def __init__(self, model, cutter): self.model = model self.diameter = 2 * cutter.radius self.height = self.diameter # TODO: enfoncement self.nb_layers = ceil( (model.maxz - model.minz) / self.height) + 1 # TODO: +1 skypocket self.layers = [] #self.heights = Grid.floatrange(model.minz, model.maxz, self.height, False) self.heights = [ model.minz + i * self.height for i in range(self.nb_layers) ] self.minx, self.miny, self.minz = model.minx, model.miny, model.minz self.maxx, self.maxy, self.maxz = model.maxx, model.maxy, model.maxz self.up_vector = Vector(0, 0, 1) self.Box = GridBox(self) self.do_pavement()
def __init__(self, path_processor, physics=None): self.pa = path_processor self._up_vector = Vector(0, 0, 1) self.physics = physics self._processed_triangles = [] if self.physics: accuracy = 20 max_depth = 16 # TODO: migrate to new interface maxx = max([m.maxx for m in self.models]) minx = max([m.minx for m in self.models]) maxy = max([m.maxy for m in self.models]) miny = max([m.miny for m in self.models]) model_dim = max(abs(maxx - minx), abs(maxy - miny)) depth = math.log(accuracy * model_dim / self.cutter.radius) / \ math.log(2) self._physics_maxdepth = min(max_depth, max(ceil(depth), 4))
def _add_cuboid_to_model(model, start, direction, height, width): up = Vector(0, 0, 1).mul(height) ortho_dir = direction.cross(up).normalized() start1 = start.add(ortho_dir.mul(-width/2)) start2 = start1.add(up) start3 = start2.add(ortho_dir.mul(width)) start4 = start3.sub(up) end1 = start1.add(direction) end2 = start2.add(direction) end3 = start3.add(direction) end4 = start4.add(direction) faces = ((start1, start2, start3, start4), (start1, end1, end2, start2), (start2, end2, end3, start3), (start3, end3, end4, start4), (start4, end4, end1, start1), (end4, end3, end2, end1)) for face in faces: t1, t2 = _get_triangles_for_face(face) model.append(t1) model.append(t2)
t = None p1 = None p2 = None p3 = None if binary: for i in range(1, numfacets + 1): if callback and callback(): log.warn("STLImporter: load model operation cancelled") return None a1 = unpack("<f", f.read(4))[0] a2 = unpack("<f", f.read(4))[0] a3 = unpack("<f", f.read(4))[0] n = Vector(float(a1), float(a2), float(a3)) v11 = unpack("<f", f.read(4))[0] v12 = unpack("<f", f.read(4))[0] v13 = unpack("<f", f.read(4))[0] p1 = UniqueVertex(float(v11), float(v12), float(v13)) v21 = unpack("<f", f.read(4))[0] v22 = unpack("<f", f.read(4))[0] v23 = unpack("<f", f.read(4))[0] p2 = UniqueVertex(float(v21), float(v22), float(v23)) v31 = unpack("<f", f.read(4))[0] v32 = unpack("<f", f.read(4))[0]
def __init__(self, radius, **kwargs): BaseCutter.__init__(self, radius, **kwargs) self.axis = Vector(0, 0, 1)
def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width, grid_direction, start_position, rounded_corners, reverse): current_location = _get_position(minx, maxx, miny, maxy, z, start_position) if line_distance > 0: line_steps_x = math.ceil((float(maxx - minx) / line_distance)) line_steps_y = math.ceil((float(maxy - miny) / line_distance)) line_distance_x = (maxx - minx) / line_steps_x line_distance_y = (maxy - miny) / line_steps_y lines = get_spiral_layer_lines(minx, maxx, miny, maxy, z, line_distance_x, line_distance_y, grid_direction, start_position, current_location) if reverse: lines.reverse() # turn the lines into steps if rounded_corners: rounded_lines = [] previous = None for index, (start, end) in enumerate(lines): radius = 0.5 * min(line_distance_x, line_distance_y) edge_vector = end.sub(start) # TODO: ellipse would be better than arc offset = edge_vector.normalized().mul(radius) if previous: start = start.add(offset) center = previous.add(offset) up_vector = previous.sub(center).cross(start.sub(center)).normalized() north = center.add(Vector(1.0, 0.0, 0.0)) angle_start = pycam.Geometry.get_angle_pi(north, center, previous, up_vector, pi_factor=True) * 180.0 angle_end = pycam.Geometry.get_angle_pi(north, center, start, up_vector, pi_factor=True) * 180.0 # TODO: remove these exceptions based on up_vector.z (get_points_of_arc does not respect the plane, yet) if up_vector.z < 0: angle_start, angle_end = -angle_end, -angle_start arc_points = pycam.Geometry.get_points_of_arc(center, radius, angle_start, angle_end) if up_vector.z < 0: arc_points.reverse() for arc_index in range(len(arc_points) - 1): p1_coord = arc_points[arc_index] p2_coord = arc_points[arc_index + 1] p1 = Point(p1_coord[0], p1_coord[1], z) p2 = Point(p2_coord[0], p2_coord[1], z) rounded_lines.append((p1, p2)) if index != len(lines) - 1: end = end.sub(offset) previous = end rounded_lines.append((start, end)) lines = rounded_lines for start, end in lines: points = [] if step_width is None: points.append(start) points.append(end) else: line = Line(start, end) if isiterable(step_width): steps = step_width else: steps = floatrange(0.0, line.len, inc=step_width) for step in steps: next_point = line.p1.add(line.dir.mul(step)) points.append(next_point) if reverse: points.reverse() yield points