def set_wait_time_on_sharp_corners(print_organizer, threshold=0.5 * math.pi, wait_time=0.3): """ Sets a wait time at the sharp corners of the path, based on the angle threshold. Parameters ---------- print_organizer: :class:`compas_slicer.print_organization.BasePrintOrganizer` threshold: float angle_threshold wait_time: float Time in seconds to introduce to add as a wait time """ number_of_wait_points = 0 for printpoint, i, j, k in print_organizer.printpoints_indices_iterator(): neighbors = print_organizer.get_printpoint_neighboring_items('layer_%d' % i, 'path_%d' % j, k) prev_ppt = neighbors[0] next_ppt = neighbors[1] if prev_ppt and next_ppt: v_to_prev = normalize_vector(Vector.from_start_end(printpoint.pt, prev_ppt.pt)) v_to_next = normalize_vector(Vector.from_start_end(printpoint.pt, next_ppt.pt)) a = abs(Vector(*v_to_prev).angle(v_to_next)) if a < threshold: printpoint.wait_time = wait_time printpoint.blend_radius = 0.0 # 0.0 blend radius for points where the robot will wait number_of_wait_points += 1 logger.info('Added wait times for %d points' % number_of_wait_points)
def is_cww(pt_A: Tuple[float], pt_B: Tuple[float], pt_C: Tuple[float]) -> bool: """ Checks if vector AB is oriented clockwise or counter-clockwise to vector AC. Parameters ---------- pt_a, pt_b, pt_c : list or tuple of floats Returns ------- bool >>> is_cww((20,30), (40,10), (12,25)) False """ # create the two vectors, but one is rotated 90 degrees # based on https://gamedev.stackexchange.com/questions/45412/ vector_AC = Vector.from_start_end((*pt_A, 0), (*pt_C, 0)) rot_B = rotate_points_xy([pt_B], radians(90), pt_A) rotated_AB = Vector.from_start_end((*pt_A, 0), *rot_B) # positive dot product ccw angle_between = dot_vectors_xy(rotated_AB, vector_AC) if angle_between > 0: return True if angle_between < 0: return False raise ValueError('Vectors have the same direction')
def is_ccw(A, B, C): Vector_AB = Vector.from_start_end(A, B) Vector_AC = Vector.from_start_end(A, C) #Tests whether rotation from AB onto AC is ccw in the xy-plane angle = Vector.angle_signed(Vector_AB, Vector_AC, (0, 0, 1)) if angle < 0: return False else: return True
def get_area(polygon): center = polygon.center Atot = 0 points = polygon.points points.append(points[0]) for i in range(len(points) - 1): u = Vector.from_start_end(points[i], points[i + 1]) v = Vector.from_start_end(points[i], center) uxv = u.cross(v) Atot += uxv.length / 2 return Atot
def get_normal_of_path_on_xy_plane(k, point, path, mesh): """ Finds the normal of the curve that lies on the xy plane at the point with index k Parameters ---------- k: int, index of the point point: :class: 'compas.geometry.Point' path: :class: 'compas_slicer.geometry.Path' mesh: :class: 'compas.datastructures.Mesh' Returns ---------- :class: 'compas.geometry.Vector' """ # find mesh normal is not really needed in the 2D case of planar slicer # instead we only need the normal of the curve based on the neighboring pts if (0 < k < len(path.points) - 1) or path.is_closed: prev_pt = path.points[k - 1] next_pt = path.points[(k + 1) % len(path.points)] v1 = np.array(normalize_vector(Vector.from_start_end(prev_pt, point))) v2 = np.array(normalize_vector(Vector.from_start_end(point, next_pt))) v = (v1 + v2) * 0.5 normal = [-v[1], v[0], v[2]] # rotate 90 degrees COUNTER-clockwise on the xy plane else: if k == 0: next_pt = path.points[k + 1] v = normalize_vector(Vector.from_start_end(point, next_pt)) normal = [-v[1], v[0], v[2] ] # rotate 90 degrees COUNTER-clockwise on the xy plane else: # k == len(path.points)-1: prev_pt = path.points[k - 1] v = normalize_vector(Vector.from_start_end(point, prev_pt)) normal = [v[1], -v[0], v[2]] # rotate 90 degrees clockwise on the xy plane if length_vector(normal) == 0: # When the neighboring elements happen to cancel out, then search for the true normal, # and project it on the xy plane for consistency normal = get_closest_mesh_normal_to_pt(mesh, point) normal = [normal[0], normal[1], 0] normal = normalize_vector(normal) normal = Vector(*list(normal)) return normal
def convex_polygon_area_compas(polygon_vertices): """Compute the area of a convex polygon using compas. Parameters ---------- polygon : sequence The XY coordinates of the vertices/corners of the polygon. The vertices are assumed to be in order. The polygon is assumed to be closed: the first and last vertex in the sequence should not be the same. Returns ------- float The area of the polygon. """ polygon = Polygon(polygon_vertices) if not polygon.is_convex: sys.exit("the polygon is not convex.") vectors = [] centroid = polygon.centroid area = 0.0 for p in polygon.points: vectors.append(Vector.from_start_end(centroid, p)) for i in range(len(vectors)): area += cross_vectors(vectors[i - 1], vectors[i])[2] / 2 return area
def convex_polygon_area(polygon): """Compute the area of a convex polygon. Parameters ---------- polygon: compas.geometry.Polygon An ordered list of points of a convex polygon. Returns ------- Float Area of the polygon. """ # find vectors from centroid to all corner points. centroid = polygon.centroid vectors = [] for vertex in polygon: vectors.append(Vector.from_start_end(centroid, vertex)) areas = [] # the magnitude of the cross product equals the area of a parallelogram, thus half of it equals the triangle. for u, v in pairwise(vectors + vectors[:1]): uxv = u.cross(v) a = uxv.length / 2 areas.append(a) return sum(areas)
def set_blend_radius(print_organizer, d_fillet=10, buffer=0.3): """Sets the blend radius (filleting) for the robotic motion. Parameters ---------- print_organizer: :class:`compas_slicer.slicers.BasePrintOrganizer` d_fillet: float Value to attempt to fillet with. Defaults to 10 mm. buffer: float Buffer to make sure that the blend radius is never too big. Defaults to 0.3. """ logger.info("Setting blend radius") for printpoint, i, j, k in print_organizer.printpoints_indices_iterator(): layer_key = 'layer_%d' % i path_key = 'path_%d' % j neighboring_items = print_organizer.get_printpoint_neighboring_items( layer_key, path_key, k) if not printpoint.wait_time: radius = d_fillet if neighboring_items[0]: radius = min( radius, norm_vector( Vector.from_start_end(neighboring_items[0].pt, printpoint.pt)) * buffer) if neighboring_items[1]: radius = min( radius, norm_vector( Vector.from_start_end(neighboring_items[1].pt, printpoint.pt)) * buffer) radius = round(radius, 5) else: radius = 0.0 # 0.0 blend radius for points where the robot will pause and wait printpoint.blend_radius = radius
def find_zero_crossing_data(self, u, v): """ Finds the position of the zero-crossing on the edge u,v. """ dist_a, dist_b = self.mesh.vertex[u]['scalar_field'], self.mesh.vertex[ v]['scalar_field'] if abs(dist_a) + abs(dist_b) > 0: v_coords_a, v_coords_b = self.mesh.vertex_coordinates( u), self.mesh.vertex_coordinates(v) vec = Vector.from_start_end(v_coords_a, v_coords_b) vec = scale_vector(vec, abs(dist_a) / (abs(dist_a) + abs(dist_b))) pt = add_vectors(v_coords_a, vec) return pt
def from_bounding_box(cls, bbox): """Construct a box from the result of a bounding box calculation. Parameters ---------- bbox : list[[float, float, float] | :class:`compas.geometry.Point`] A list of 8 point locations, representing the corners of the bounding box. Positions 0, 1, 2, 3 are the bottom corners. Positions 4, 5, 6, 7 are the top corners. Both the top and bottom face are oriented in CCW direction, starting at the bottom, left-most point. Returns ------- :class:`compas.geometry.Box` The box shape. Examples -------- >>> from compas.geometry import bounding_box >>> bbox = bounding_box([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]) >>> box = Box.from_bounding_box(bbox) >>> box.width 1.0 >>> box.height 1.0 >>> box.depth 1.0 """ a = bbox[0] b = bbox[1] d = bbox[3] e = bbox[4] xaxis = Vector.from_start_end(a, b) yaxis = Vector.from_start_end(a, d) zaxis = Vector.from_start_end(a, e) xsize = xaxis.length ysize = yaxis.length zsize = zaxis.length frame = Frame(centroid_points(bbox), xaxis, yaxis) return cls(frame, xsize, ysize, zsize)
def get_layer_ppts(self, layer, base_boundary): """ Creates the PrintPoints of a single layer.""" max_layer_height = get_param(self.parameters, key='max_layer_height', defaults_type='layers') min_layer_height = get_param(self.parameters, key='min_layer_height', defaults_type='layers') avg_layer_height = get_param(self.parameters, 'avg_layer_height', 'layers') all_pts = [pt for path in layer.paths for pt in path.points] closest_fks, projected_pts = utils.pull_pts_to_mesh_faces( self.slicer.mesh, all_pts) normals = [ Vector(*self.slicer.mesh.face_normal(fkey)) for fkey in closest_fks ] count = 0 crv_to_check = Path( base_boundary.points, True) # creation of fake path for the lower boundary layer_ppts = {} for i, path in enumerate(layer.paths): layer_ppts['path_%d' % i] = [] for p in path.points: cp = closest_point_on_polyline(p, Polyline(crv_to_check.points)) d = distance_point_point(cp, p) ppt = PrintPoint(pt=p, layer_height=avg_layer_height, mesh_normal=normals[count]) ppt.closest_support_pt = Point(*cp) ppt.distance_to_support = d ppt.layer_height = max(min(d, max_layer_height), min_layer_height) ppt.up_vector = Vector( *normalize_vector(Vector.from_start_end(cp, p))) ppt.frame = ppt.get_frame() layer_ppts['path_%d' % i].append(ppt) count += 1 crv_to_check = path return layer_ppts
def get_up_vectors(self): """ Finds the up_vectors of each point of the boundary. A smoothing step is also included. """ up_vectors = [] for i, p in enumerate(self.points): v1 = Vector.from_start_end(p, self.points[(i + 1) % len(self.points)]) cross = v1.cross(self.normals[i]) v = Vector(*normalize_vector(cross)) if v[2] < 0: v.scale(-1) up_vectors.append(v) up_vectors = utils.smooth_vectors(up_vectors, strength=0.4, iterations=3) return up_vectors
def seams_smooth(slicer, smooth_distance): """Smooths the seams (transition between layers) by removing points within a certain distance. Parameters ---------- slicer: :class:`compas_slicer.slicers.BaseSlicer` An instance of one of the compas_slicer.slicers classes. smooth_distance: float Distance (in mm) to perform smoothing """ logger.info("Smoothing seams with a distance of %i mm" % smooth_distance) for i, layer in enumerate(slicer.layers): if len(layer.paths) == 1 or isinstance( layer, compas_slicer.geometry.VerticalLayer): for path in layer.paths: pt0 = path.points[0] # only points in the first half of a path should be evaluated half_of_path = path.points[:int(len(path.points) / 2)] for point in half_of_path: if distance_point_point(pt0, point) < smooth_distance: # remove points if within smooth_distance path.points.pop(0) else: # create new point at a distance of the # 'smooth_distance' from the first point, # so that all seams are of equal length vect = Vector.from_start_end(pt0, point) vect.unitize() new_pt = pt0 + (vect * smooth_distance) path.points.insert(0, new_pt) break else: logger.warning( "Smooth seams only works for layers consisting out of a single path, or for vertical layers." "\nPaths were not changed, seam smoothing skipped for layer %i" % i)
class SkeletonVol(Mesh): """ SkeletonVol is typologically constructed low poly mesh. It construct a branch like volumetric mesh from input lines. """ def __init__(self): super(SkeletonVol, self).__init__() @classmethod def from_skeleton_lines(cls, lines=[]): skeleton_vol = cls() network = Network.from_lines(lines) convex_hull_mesh = get_convex_hull_mesh(points) def get_convex_hull_mesh(points): faces = convex_hull(points) vertices = list(set(flatten(faces))) i_index = {i: index for index, i in enumerate(vertices)} vertices = [points[index] for index in vertices] faces = [[i_index[i] for i in face] for face in faces] faces = unify_cycles(vertices, faces) mesh = Mesh.from_vertices_and_faces(vertices, faces) return mesh guids = compas_rhino.select_lines() lines = compas_rhino.get_line_coordinates(guids) network = Network.from_lines(lines) leafs = [] joints = [] for key in network.node: if network.is_leaf(key): leafs.append(key) else: joints.append(key) pt_center = network.node_coordinates(joints[0]) pts = [network.node_coordinates(key) for key in leafs] convex_hull_mesh = get_convex_hull_mesh(pts) mesh = Mesh() # for key in convex_hull_mesh.vertices(): # mesh.add_vertex(key) # mesh.vertex[key].update(convex_hull_mesh.vertex[key]) descdent_tree = copy.deepcopy(convex_hull_mesh.halfedge) for u, v in convex_hull_mesh.edges(): descdent_tree[u][v] = {'jp': None, 'lp': None} descdent_tree[v][u] = {'jp': None, 'lp': None} # current_key = convex_hull_mesh.number_of_vertices() current_key = 0 for fkey in convex_hull_mesh.faces(): f_centroid = convex_hull_mesh.face_centroid(fkey) vec = Vector.from_start_end(pt_center, f_centroid) # if the branches has a 'convex' corner, # flip the vec to the corresponding face. f_normal = convex_hull_mesh.face_normal(fkey) angle = angle_vectors(f_normal, vec, False) if angle > math.pi * 0.5: pln = Plane(pt_center, f_normal) pt_mirror = mirror_point_plane(f_centroid, pln) vec = Vector.from_start_end(pt_center, pt_mirror) vec.unitize() vec.scale(joint_width) pt = add_vectors(pt_center, vec) face = convex_hull_mesh.face[fkey] v_keys = face + [face[0]] for u, v in pairwise(v_keys): descdent_tree[u][v].update({'jp': current_key}) mesh.add_vertex(current_key) mesh.vertex[current_key].update({'x': pt[0], 'y': pt[1], 'z': pt[2]}) current_key += 1 for key in convex_hull_mesh.vertices(): nbrs = convex_hull_mesh.vertex_neighbors(key) for nbr in nbrs: halfedge = (key, nbr) pt_joint_descendent = mesh.vertex_coordinates( descdent_tree[key][nbr]['jp']) vec_edge = Vector.from_start_end( pt_center, convex_hull_mesh.vertex_coordinates(key)) pln_end = Plane(convex_hull_mesh.vertex_coordinates(key), vec_edge) pt = project_point_plane(pt_joint_descendent, pln_end) vec_leaf = Vector.from_start_end( convex_hull_mesh.vertex_coordinates(key), pt) vec_leaf.unitize() vec_leaf.scale(leaf_width) pt = add_vectors(convex_hull_mesh.vertex_coordinates(key), vec_leaf) descdent_tree[key][nbr].update({'lp': current_key}) mesh.add_vertex(current_key) mesh.vertex[current_key].update({ 'x': pt[0], 'y': pt[1], 'z': pt[2] }) current_key += 1 for key in convex_hull_mesh.vertices(): nbrs = convex_hull_mesh.vertex_neighbors(key, ordered=True) v_keys = nbrs + [nbrs[0]] for a, b in pairwise(v_keys): face = [ descdent_tree[key][a]['lp'], descdent_tree[key][a]['jp'], descdent_tree[key][b]['jp'], descdent_tree[key][b]['lp'] ] mesh.add_face(face) fixed = list(mesh.vertices_where({'vertex_degree': 3})) fixed = list(mesh.vertices()) mesh = mesh_subdivide_catmullclark(mesh, k=1, fixed=fixed) # mesh = mesh_subdivide_quad(mesh, k=1) mesh_smooth_centroid(mesh, fixed=fixed) artist = MeshArtist(mesh) artist.draw_mesh()
mesh = Mesh() for key in convex_hull_mesh.vertices(): mesh.add_vertex(key) mesh.vertex[key].update(convex_hull_mesh.vertex[key]) descdent_tree = copy.deepcopy(convex_hull_mesh.halfedge) for u, v in convex_hull_mesh.edges(): descdent_tree[u][v] = {'jp': None, 'lp': None} descdent_tree[v][u] = {'jp': None, 'lp': None} current_key = convex_hull_mesh.number_of_vertices() for fkey in convex_hull_mesh.faces(): f_centroid = convex_hull_mesh.face_centroid(fkey) vec = Vector.from_start_end(pt_center, f_centroid) # if the branches has a 'convex' corner, # flip the vec to the corresponding face. f_normal = convex_hull_mesh.face_normal(fkey) angle = angle_vectors(f_normal, vec, False) if angle > math.pi * 0.5: # vec.scale(-1) pln = Plane(pt_center, f_normal) pt_mirror = mirror_point_plane(f_centroid, pln) vec = Vector.from_start_end(pt_center, pt_mirror) # angle = math.pi - angle vec.unitize() # dist = joint_width / math.cos(angle)
from compas.geometry import Frame from compas.geometry import Plane from compas.geometry import Vector a = Vector(1, 0, 0) b = Vector.from_start_end([1, 0, 0], [2, 0, 0]) assert a == b a = Plane([0, 0, 0], [0, 0, 1]) b = Plane.from_three_points([0, 0, 0], [1, 0, 0], [0, 1, 0]) assert a == b a = Frame([0, 0, 0], [3, 0, 0], [0, 2, 0]) b = Frame.from_points([0, 0, 0], [5, 0, 0], [1, 2, 0]) assert a == b
data['positive_y_axis'] = is_positive_y # distance from plane - Scalar value (per vertex) mesh.update_default_vertex_attributes({'dist_from_plane': 0.0}) plane = (Point(0.0, 0.0, -30.0), Vector(0.0, 0.5, 0.5)) for v_key, data in mesh.vertices(data=True): v_coord = mesh.vertex_coordinates(v_key, axes='xyz') data['dist_from_plane'] = distance_point_plane(v_coord, plane) # direction towards point - Vector value (per vertex) mesh.update_default_vertex_attributes({'direction_to_pt': 0.0}) pt = Point(4.0, 1.0, 0.0) for v_key, data in mesh.vertices(data=True): v_coord = mesh.vertex_coordinates(v_key, axes='xyz') data['direction_to_pt'] = np.array( normalize_vector(Vector.from_start_end(v_coord, pt))) # --------------- Slice mesh slicer = PlanarSlicer(mesh, slicer_type="default", layer_height=5.0) slicer.slice_model() simplify_paths_rdp_igl(slicer, threshold=1.0) slicer_utils.save_to_json(slicer.to_data(), OUTPUT_PATH, 'slicer_data.json') # --------------- Create printpoints print_organizer = PlanarPrintOrganizer(slicer) print_organizer.create_printpoints() # --------------- Transfer mesh attributes to printpoints transfer_mesh_attributes_to_printpoints(mesh, print_organizer.printpoints_dict)
3. Define a function for computing the cross products of two same-length arrays of vectors by a. Prototype in pure Python (loop over the arrays). b. Make Numpy equivalent without loops. """ # 2. Use the cross product to compute the area of a convex, 2D polygon from the following set of points, # and compare your result with using the compas function area_triangle. from compas.geometry import Vector from compas.geometry import area_triangle a = [0.0, 0.0, 0.0] b = [1.0, 0.0, 0.0] c = [0.0, 1.0, 0.0] ab = Vector.from_start_end(a, b) #Vector1 ac = Vector.from_start_end(a, c) #Vector2 #compute the cross product using the Vector function cross cp = a.cross(b) x = #... #take te length of the vector L = #... #and divide L by 2 A1 = #... print(A1) #now compute the area by using the area_triangle function of compas A2 = #... print(A2)
from compas.geometry import Vector from compas.geometry import area_triangle a = [0.0, 0.0, 0.0] b = [1.0, 0.0, 0.0] c = [0.0, 1.0, 0.0] ab = Vector.from_start_end(a, b) ac = Vector.from_start_end(a, c) L = ab.cross(ac).length A = area_triangle([a, b, c]) print(0.5 * L == A)