def smoothen_cut(zero_contours, mesh, saddle_vkeys, iterations, strength, distance_threshold=20.0 * 20.0): """ Iterative smoothing of the cut around the saddle point. """ for _ in range(iterations): saddles = [mesh.vertex_coordinates(key) for key in saddle_vkeys] count = 0 for cluster_key in zero_contours.sorted_point_clusters: pts = zero_contours.sorted_point_clusters[cluster_key] edges = zero_contours.sorted_edge_clusters[cluster_key] for i, pt in enumerate(pts): if 0.01 < min( [distance_point_point_sqrd(pt, s) for s in saddles]) < distance_threshold: count += 1 edge = edges[i] prev = pts[i - 1] next_p = pts[(i + 1) % len(pts)] avg = [(prev[0] + next_p[0]) * 0.5, (prev[1] + next_p[1]) * 0.5, (prev[2] + next_p[2]) * 0.5] point = np.array(avg) * strength + np.array(pt) * ( 1 - strength) line = Line(mesh.vertex_coordinates(edge[0]), mesh.vertex_coordinates(edge[1])) projected_pt = project_point_line(point, line) pts[i] = projected_pt zero_contours.sorted_point_clusters[cluster_key][ i] = projected_pt return zero_contours
def try_to_create_connection(G, isoV, ei, ej, i, j, side_i, side_j, connections_found, tol): vi = isoV[ei[side_i]] vj = isoV[ej[side_j]] if distance_point_point_sqrd(vi, vj) < tol: G.add_edge(i, j) connections_found[i][side_i] = True connections_found[j][side_j] = True return True return False
def close_paths(self): """ For paths that are labeled as closed, it makes sure that the first and the last point are identical. """ for layer in self.layers: for path in layer.paths: if path.is_closed: # if the path is closed, first and last point should be the same. if distance_point_point_sqrd( path.points[0], path.points[-1] ) > 0.00001: # if not already the same path.points.append(path.points[0])
def is_valid(self): if len(self.sorted_point_clusters) > 0: for key in self.sorted_point_clusters: pts = self.sorted_point_clusters[key] if len(pts) > 3: path_total_length = 0.0 for p1, p2 in pairwise(pts): path_total_length += distance_point_point_sqrd(p1, p2) if path_total_length > 1.0: return True # make sure there is at least one path with acceptable length return False
def restore_mesh_attributes(mesh, v_attributes_dict): """ Restores the cut and boundary attributes on the mesh vertices from the dictionary of the previously saved attributes """ mesh.update_default_vertex_attributes({'boundary': 0}) mesh.update_default_vertex_attributes({'cut': 0}) D_THRESHOLD = 0.01 welded_mesh_vertices = [] indices_to_vkeys = {} for i, vkey in enumerate(mesh.vertices()): v_coords = mesh.vertex_coordinates(vkey) pt = Point(x=v_coords[0], y=v_coords[1], z=v_coords[2]) welded_mesh_vertices.append(pt) indices_to_vkeys[i] = vkey for v_coords in v_attributes_dict['boundary_1']: closest_index = utils.get_closest_pt_index(pt=v_coords, pts=welded_mesh_vertices) c_vkey = indices_to_vkeys[closest_index] if distance_point_point_sqrd( v_coords, mesh.vertex_coordinates(c_vkey)) < D_THRESHOLD: mesh.vertex_attribute(c_vkey, 'boundary', value=1) for v_coords in v_attributes_dict['boundary_2']: closest_index = utils.get_closest_pt_index(pt=v_coords, pts=welded_mesh_vertices) c_vkey = indices_to_vkeys[closest_index] if distance_point_point_sqrd( v_coords, mesh.vertex_coordinates(c_vkey)) < D_THRESHOLD: mesh.vertex_attribute(c_vkey, 'boundary', value=2) for cut_index in v_attributes_dict['cut']: for v_coords in v_attributes_dict['cut'][cut_index]: closest_index = utils.get_closest_pt_index( pt=v_coords, pts=welded_mesh_vertices) c_vkey = indices_to_vkeys[closest_index] if distance_point_point_sqrd( v_coords, mesh.vertex_coordinates(c_vkey)) < D_THRESHOLD: mesh.vertex_attribute(c_vkey, 'cut', value=int(cut_index))
def are_neighboring_point_clouds(pts1, pts2, threshold): """ Returns True if 3 or more points of the point clouds are closer than the threshold. False otherwise. Parameters ---------- pts1: list, :class: 'compas.geometry.Point' pts2: list, :class: 'compas.geometry.Point' threshold: float """ count = 0 for pt in pts1: if distance_point_point_sqrd(pt, utils.get_closest_pt(pt, pts2)) < threshold: count += 1 if count > 2: return True return False
def search(node): if node is None: return d2 = distance_point_point_sqrd(point, node.point) if d2 < best[2]: if node.label not in exclude: best[:] = node.point, node.label, d2 d = point[node.axis] - node.point[node.axis] if d <= 0: close, far = node.left, node.right else: close, far = node.right, node.left search(close) if d ** 2 < best[2]: search(far)
def recursive_search(node): if node is None: return xyz, axis, label, left, right = node d2 = distance_point_point_sqrd(point, xyz) if d2 < best[2] and label not in exclude: best[:] = xyz, label, d2 diff = point[axis] - xyz[axis] if diff <= 0: close, far = left, right else: close, far = right, left recursive_search(close) if diff**2 < best[2]: recursive_search(far)
def get_closest_mesh_vkey_to_pt(mesh, pt): """ Finds the vertex key that is the closest to the point. Parameters ---------- mesh: :class: 'compas.datastructures.Mesh' pt: :class: 'compas.geometry.Point' Returns ---------- int the closest vertex key """ # cloud = [Point(data['x'], data['y'], data['z']) for v_key, data in mesh.vertices(data=True)] # closest_index = compas.geometry.closest_point_in_cloud(pt, cloud)[2] vertex_tupples = [(v_key, Point(data['x'], data['y'], data['z'])) for v_key, data in mesh.vertices(data=True)] vertex_tupples = sorted( vertex_tupples, key=lambda v_tupple: distance_point_point_sqrd(pt, v_tupple[1])) closest_vkey = vertex_tupples[0][0] return closest_vkey
def is_true_mesh_adjacency(all_meshes, key1, key2): """ Returns True if the two meshes share 3 or more vertices. False otherwise. Parameters ---------- all_meshes: list, :class: 'compas.datastructures.Mesh' key1: int, index of mesh1 key2: int, index of mesh2 """ count = 0 mesh1 = all_meshes[key1] mesh2 = all_meshes[key2] pts_mesh2 = [mesh2.vertex_coordinates(vkey) for vkey, data in mesh2.vertices(data=True) if (data['cut'] > 0 or data['boundary'] > 0)] for vkey, data in mesh1.vertices(data=True): if data['cut'] > 0 or data['boundary'] > 0: pt = mesh1.vertex_coordinates(vkey) ci = utils.get_closest_pt_index(pt, pts_mesh2) if distance_point_point_sqrd(pt, pts_mesh2[ci]) < 0.00001: count += 1 if count == 3: return True return False
def create_planar_paths_igl(mesh, n): """ Creates planar contours using the libigl function: https://libigl.github.io/libigl-python-bindings/igl_docs/#isolines TODO: ??? It is currently the only method that can return identify OPEN versus CLOSED paths. Parameters ---------- mesh: :class: 'compas.datastructures.Mesh' The mesh to be sliced n: number of contours """ utils.check_package_is_installed('igl') import igl v, f = mesh.to_vertices_and_faces() ds = np.array([vertex[2] for vertex in v]) # save distances of each vertex from the floor: this will be the scalar field for the slicing operation. # --- generate disconnected segments of isolines using the libigl function v = np.array(v) f = np.array(f) isoV, isoE = igl.isolines(v, f, ds, n) # --- group resulting segments per level print('Grouping segments per level') segments_per_level = {} tol = 1e-6 for e in isoE: v0 = isoV[e[0]] v1 = isoV[e[1]] assert(abs(v0[2] - v1[2]) < tol) h = v0[2] found = False for key in segments_per_level: if abs(key - h) < tol: if e[0] != e[1]: # do not add null segments segments_per_level[key].append(e) found = True if not found: segments_per_level[h] = [e] #assert(len(segments_per_level) == n) utils.save_to_json(utils.point_list_to_dict(isoV), "/examples/1_planar_slicing_simple/data/output", 'isoV.json') sorted_keys = [key for key in segments_per_level] sorted(sorted_keys) layers = [] # --- Create connectivity graph G for every group, and sort edges based on G with progressbar.ProgressBar(max_value=len(segments_per_level)) as bar: for index, key in enumerate(sorted_keys): #print(' Creating connectivity for level z = ', key) es = segments_per_level[key] # list of the current edges, which whose indices we are working below G = nx.Graph() connections_found = [[False, False] for _ in es] for i, e in enumerate(es): G.add_node(i) # node, attribute for i, ei in enumerate(es): # ei here is the edge, not the index! for side_i in range(2): if not connections_found[i][side_i]: for jj, ej in enumerate(es[i+1:]): # ej here is the edge, not the index! j = i + jj + 1 for side_j in range(2): if not connections_found[j][side_j]: vi = isoV[ei[side_i]] vj = isoV[ej[side_j]] if distance_point_point_sqrd(vi, vj) < tol: G.add_edge(i, j) connections_found[i][side_i] = True connections_found[j][side_j] = True # sort connected components of G sorted_indices_dict = sort_graph_connected_components(G) # for i, s_key in enumerate(sorted_indices_dict): # sorted_eis = sorted_indices_dict[s_key] # this_segment_pts = [] # # for j, ei in enumerate(sorted_eis): # e = es[ei] # # segment pts # for k in range(2): # this_segment_pts.append(isoV[e[k]]) # # utils.save_to_json(utils.point_list_to_dict(this_segment_pts), # "C:/dev/compas_slicer/examples/1_planar_slicing_simple/data/output", 'isoV.json') # print('saved') # get points list from sorted edge indices (for each connected component) paths_pts = [[] for _ in sorted_indices_dict] are_closed = [True for _ in sorted_indices_dict] for i, s_key in enumerate(sorted_indices_dict): sorted_indices = sorted_indices_dict[s_key] for j, s_ei in enumerate(sorted_indices): v0 = isoV[es[s_ei][0]] v1 = isoV[es[s_ei][1]] if j == 0: s_ei_next = sorted_indices[j+1] v0_next = isoV[es[s_ei_next][0]] v1_next = isoV[es[s_ei_next][1]] v_mid_next = (Point(*v0_next) + Point(*v1_next)) * 0.5 if distance_point_point_sqrd(v0, v_mid_next) < distance_point_point_sqrd(v1, v_mid_next): paths_pts[i].extend([Point(*v1), Point(*v0)]) else: paths_pts[i].extend([Point(*v0), Point(*v1)]) else: v_prev = paths_pts[i][-1] if distance_point_point_sqrd(v0, v_prev) < distance_point_point_sqrd(v1, v_prev): paths_pts[i].append(Point(*v1)) else: paths_pts[i].append(Point(*v0)) if distance_point_point_sqrd(paths_pts[i][0], paths_pts[i][1]) > tol: are_closed[i] = False paths = [] for i in range(len(sorted_indices_dict)): paths.append(Path(points=paths_pts[i], is_closed=are_closed[i])) layers.append(Layer(paths)) bar.update(index) return layers