def face_flatness(self, face, maxdev=0.02): """Compute the flatness of a face. Parameters ---------- face : int The identifier of the face. Returns ------- float The flatness. Note ---- compas.geometry.mesh_flatness function currently only works for quadrilateral faces. This function uses the distance between each face vertex and its projected point on the best-fit plane of the face as the flatness metric. """ deviation = 0 polygon = self.face_coordinates(face) plane = bestfit_plane(polygon) for pt in polygon: pt_proj = project_point_plane(pt, plane) dev = distance_point_point(pt, pt_proj) if dev > deviation: deviation = dev return deviation
def calc_correction_vector_tip(pt_new, base_pts): """Computing correction vector to meet the distance threshold. return vector P-P_c (figure below). .. image:: ../images/vertex_correction_to_top_connection.png :scale: 80 % :align: center Parameters ---------- pt_new : [type] [description] base_pts : list of three points contact base points for the base bars """ assert len(base_pts) == 3 vec_x = normalize_vector(vector_from_points(base_pts[0], base_pts[1])) vec_y = normalize_vector(vector_from_points(base_pts[0], base_pts[2])) vec_z = normalize_vector(cross_vectors(vec_x, vec_y)) pl_test = (base_pts[0], vec_z) dist_p = distance_point_plane(pt_new, pl_test) pt_proj = project_point_plane(pt_new, pl_test) if dist_p < NODE_CORRECTION_TOP_DISTANCE: vec_m = scale_vector(normalize_vector(vector_from_points(pt_proj, pt_new)), NODE_CORRECTION_TOP_DISTANCE) pt_n = add_vectors(pt_proj, vec_m) else: pt_n = None return pt_n
def polygon_flatness(polygon): """Comput the flatness of a polygon. Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- float The flatness. Note ---- compas.geometry.mesh_flatness function currently only works for quadrilateral faces. This function uses the distance between each face vertex and its projected point on the best-fit plane of the face as the flatness metric. """ deviation = 0 plane = bestfit_plane(polygon) for pt in polygon: pt_proj = project_point_plane(pt, plane) dev = distance_point_point(pt, pt_proj) if dev > deviation: deviation = dev return deviation
def correct_angle(pt_new, pt_int, pl_test): """Computing correction vector to meet the angle threshold. return vector P-P_c (figure below). .. image:: ../images/vertex_correction_to_base.png :scale: 80 % :align: center Parameters ---------- pt_new : point new point P pt_int : point contact point between two bars, i.e. point N in the image above pl_test : tuple (point, vector) the grey plane shown in the image above Returns ------- tuple of two points return None if feasible (bigger than the angle threshold), otherwise return the line connecting pt_int and modified pt """ pt_proj = project_point_plane(pt_new, pl_test) sin_ang = distance_point_point(pt_new, pt_proj)/distance_point_point(pt_new, pt_int) if sin_ang < NODE_CORRECTION_SINE_ANGLE: # ? why 0.3 here? # length of the triangle's hypotenuse # dist_n = 0.3 * distance_point_point(pt_new, pt_int) dist_n = NODE_CORRECTION_SINE_ANGLE * distance_point_point(pt_new, pt_int) # modified point Pc pt_m = add_vectors(pt_proj, scale_vector(normalize_vector(vector_from_points(pt_proj, pt_new)), dist_n)) lin_c = (pt_int, pt_m) return lin_c else: return None
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, mesh.vertex_coordinates(key)) pln_end = Plane(mesh.vertex_coordinates(key), vec_edge) pt = project_point_plane(pt_joint_descendent, pln_end) vec_leaf = Vector.from_start_end(mesh.vertex_coordinates(key), pt) vec_leaf.unitize() vec_leaf.scale(leaf_width) pt = add_vectors(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):
def project_to_plane(self, plane): plane = (plane.point, plane.normal) return project_point_plane(self, plane)
def test_project_point_plane(): assert allclose(project_point_plane([0, 2.5, 2], ([3, 4, 5], [6, 7, 8.8])), [2.0278256587047525, 4.865796601822211, 4.974144299433638])
def cell_planarise(cell, kmax=100, target_centers={}, target_normals={}, target_areas={}, fix_vkeys=[], avg_fkeys=[], collapse_edge_length=0.1, tolerance_flat=0.001, tolerance_area=0.001, callback=None, callback_args=None, print_result_info=False): """Planarise the faces of a cell. Planarisation of a cell is implemented as a three-step iterative procedure. At every iteration, each face is first individually projected to its best-fit plane (unless a target normal is given). Then, each face is re-sized to its target area (if given). Finally, the new vertex coordinates are computed by taking the centroid of the disconnected corners of the faces. Parameters ---------- cell : Mesh A mesh object kmax : int, optional [100] Number of iterations. target_face_areas : dictionary, optional [{}] A dictionary of fkeys and target areas. target_face_normals : dictionary, optional [{}] A dictionary of fkeys and target face normals. target_face_centers : dictionary, optional [{}] A dictionary of fkeys and target face centers. fix_vkeys : list, optional [[]] List of vkeys to omit from arearisation. avg_fkeys : list, optional [[]] List of fkeys to average areas. collapse_edge_length : real, optional [0.1] Minimum length of edge to collapse. tolerance_flat: float, optional Convergence tolerance for face flatness. tolerance_area: float, optional Convergence tolerance for face areas. callback : callable, optional A user-defined callback function to be executed after every iteration. Default is ``None``. callback_args : tuple, optional Additional parameters to be passed to the callback. Default is ``None``. Raises ------ Exception If a callback is provided, but it is not callable. .. seealso :: `compas.geometry.mesh_planarize_faces` """ if callback: if not callable(callback): raise Exception('Callback is not callable.') # -------------------------------------------------------------------------- # 1. initialise # -------------------------------------------------------------------------- free_vkeys = list(set(cell.vertex) - set(fix_vkeys)) # -------------------------------------------------------------------------- # 2. loop # -------------------------------------------------------------------------- for k in range(kmax): deviation_flat = 0 deviation_area = 0 deviation_perp = 0 new_xyz = {vkey: [] for vkey in cell.vertex} # faces to be averaged ------------------------------------------------- if avg_fkeys: avg_face_area = _avg_face_area(cell, avg_fkeys) for fkey in cell.faces(): # evaluate current face -------------------------------------------- f_vkeys = cell.face_vertices(fkey) f_v_xyz = cell.face_coordinates(fkey) f_normal = cell.face_normal(fkey) f_area = cell.face_area(fkey) f_center = cell.face_centroid(fkey) if fkey in target_centers: f_center = target_centers[fkey] if fkey in target_normals: target_normal = target_normals[fkey] # perpness deviation perpness = 1 - abs(dot_vectors(f_normal, target_normal)) if perpness > deviation_perp: deviation_perp = perpness f_normal = target_normal # projection plane ------------------------------------------------- plane = (f_center, f_normal) # ------------------------------------------------------------------ # 3. planarise # ------------------------------------------------------------------ projected_face = [] for xyz in f_v_xyz: projected_xyz = project_point_plane(xyz, plane) projected_face.append(projected_xyz) # planarisation deviation dist = distance_point_point(xyz, projected_xyz) if dist > deviation_flat: deviation_flat = dist # ------------------------------------------------------------------ # 4. arearise # ------------------------------------------------------------------ if fkey in target_areas: target_area = target_areas[fkey] else: target_area = f_area if fkey in avg_fkeys: target_area = avg_face_area # scale factor ----------------------------------------------------- if target_area != 0: scale = (target_area / f_area)**0.5 elif target_area == 0: scale = 1 - f_area * 0.1 # scale = (target_area / f_area) ** 0.5 # scale = 0.9 # scale ------------------------------------------------------------ scaled_face = scale_polygon(projected_face, scale) # arearisation deviation areaness = abs(f_area - target_area) if areaness > deviation_area: deviation_area = areaness # collect new coordinates for i in range(len(f_vkeys)): new_xyz[f_vkeys[i]].append(scaled_face[i]) # ---------------------------------------------------------------------- # 5. compute new cell vertex coordinates # ---------------------------------------------------------------------- for vkey in free_vkeys: final_xyz = centroid_points(new_xyz[vkey]) cell.vertex_update_xyz(vkey, final_xyz) for u, v in cell.edges(): cell_collapse_short_edge(cell, u, v, min_length=collapse_edge_length) # ---------------------------------------------------------------------- # 7. check convergence # ---------------------------------------------------------------------- if deviation_flat < tolerance_flat and deviation_area < tolerance_area: if print_result_info: name = "Cell planarisation" deviation = deviation_flat if target_areas: name = "Cell arearisation" deviation = deviation_area print_result(name, k, deviation) break # callback / conduit --------------------------------------------------- if callback: callback(cell, k, callback_args)
def volmesh_planarise(volmesh, kmax=100, target_centers={}, target_normals={}, target_areas={}, fix_vkeys=[], fix_boundary_normals=False, fix_all_normals=False, min_area=None, max_area=None, tolerance_flat=0.001, tolerance_area=0.001, tolerance_perp=0.001, callback=None, callback_args=None, print_result_info=False): """Planarises the halffaces of a volmesh. Planarisation of a volmesh is implemented as a three-step iterative procedure. At every iteration, each halfface is first individually projected to its best-fit plane (unless a target normal is given). Then, each halfface is re-sized to its target area (if given). Finally, the new vertex coordinates are computed by taking the centroid of the disconnected corners of the halffaces. Parameters ---------- volmesh : VolMesh A volmesh object. kmax : int, optional [100] Number of iterations. target_face_areas : dictionary, optional [{}] A dictionary of fkeys and target areas. target_face_normals : dictionary, optional [{}] A dictionary of fkeys and target face normals. target_face_centers : dictionary, optional [{}] A dictionary of fkeys and target face centers. omit_vkeys : list, optional [[]] List of vkeys to omit from arearisation. fix_boundary_face_normals : boolean, optional [False] Whether to keep the initial normals of the bondary faces. fix_all_face_normals : boolean, optional [False] Whether to keep the initial normals of all faces. tolerance_flat: float, optional Convergence tolerance for face flatness. tolerance_area: float, optional Convergence tolerance for face areas against target areas. tolerance_perp: float, optional Convergence tolerance for face perpendicularity against target normals. callback : callable, optional A user-defined callback function to be executed after every iteration. Default is ``None``. callback_args : tuple, optional Additional parameters to be passed to the callback. Default is ``None``. print_result_info : bool, optional If True, print the result of the algorithm. Raises ------ Exception If a callback is provided, but it is not callable. .. seealso :: `compas.geometry.mesh_planarize_faces` """ if callback: if not callable(callback): raise Exception('Callback is not callable.') # -------------------------------------------------------------------------- # 1. initialise # -------------------------------------------------------------------------- free_vkeys = list(set(volmesh.vertices()) - set(fix_vkeys)) initial_normals = _get_current_volmesh_normals(volmesh) boundary_fkeys = volmesh.halffaces_on_boundaries() # -------------------------------------------------------------------------- # 2. loop # -------------------------------------------------------------------------- for k in range(kmax): deviation_flat = 0 deviation_area = 0 deviation_perp = 0 new_xyz = {vkey: [] for vkey in volmesh.vertices()} for fkey in volmesh.faces(): fkey_pair = volmesh.halfface_opposite_halfface(fkey) # evaluate current face -------------------------------------------- f_vkeys = volmesh.halfface_vertices(fkey) f_v_xyz = volmesh.halfface_coordinates(fkey) f_center = volmesh.halfface_center(fkey) f_normal = volmesh.halfface_normal(fkey) f_area = volmesh.halfface_area(fkey) # override with manual target values ------------------------------- if _pair_membership(fkey, fkey_pair, target_centers): f_center = target_centers[fkey] if _pair_membership(fkey, fkey_pair, target_normals): target_normal = target_normals[fkey] # perpness deviation perpness = 1 - abs(dot_vectors(f_normal, target_normal)) if perpness > deviation_perp: deviation_perp = perpness f_normal = target_normal if fix_boundary_normals: if fkey in boundary_fkeys: f_normal = initial_normals[fkey]['normal'] if fix_all_normals: f_normal = initial_normals[fkey]['normal'] # projection plane ------------------------------------------------- plane = (f_center, f_normal) # ------------------------------------------------------------------ # 3. planarise # ------------------------------------------------------------------ new_face = [] for xyz in f_v_xyz: projected_xyz = project_point_plane(xyz, plane) new_face.append(projected_xyz) # planarisation deviation flatness = distance_point_point(xyz, projected_xyz) if flatness > deviation_flat: deviation_flat = flatness # ------------------------------------------------------------------ # 4. arearise # ------------------------------------------------------------------ if target_areas: if fkey in target_areas: target_area = target_areas[fkey] scale = (target_area / f_area)**0.5 # scale new_face = scale_polygon(new_face, scale) # arearisation deviation areaness = abs(f_area - target_area) if areaness > deviation_area: deviation_area = areaness # collect new coordinates ------------------------------------------ for i in range(len(f_vkeys)): new_xyz[f_vkeys[i]].append(new_face[i]) # ---------------------------------------------------------------------- # 5. compute new volmesh vertex coordinates # ---------------------------------------------------------------------- for vkey in free_vkeys: final_xyz = centroid_points(new_xyz[vkey]) volmesh.vertex_update_xyz(vkey, final_xyz) # ---------------------------------------------------------------------- # 6. check convergence # ---------------------------------------------------------------------- if deviation_flat < tolerance_flat and deviation_area < tolerance_area and deviation_perp < tolerance_perp: if print_result_info: name = "Volmesh planarisation" deviation = deviation_flat if target_areas: name = "Volmesh arearisation" deviation = deviation_area print_result(name, k, deviation) break # callback / conduit --------------------------------------------------- if callback: callback(volmesh, k, callback_args)
ring_w = mesh.vertex_attribute(w, 'ring') if not ring_w: continue length_uv = mesh.edge_length(u, v) size_uv = str(mesh.edge_attribute((u, v), 'size')) radius_w = mesh.attributes['radius'][ring_w] xyz_u = mesh.vertex_coordinates(u) xyz_w = mesh.vertex_coordinates(w) normal_w = mesh.vertex_normal(w) plane_w = xyz_w, normal_w u_on_plane_w = project_point_plane(xyz_u, plane_w) wu_dir = normalize_vector(subtract_vectors(u_on_plane_w, xyz_w)) xyz_w_1 = add_vectors(xyz_w, scale_vector(wu_dir, radius_w)) uw_dir_real = normalize_vector(subtract_vectors(xyz_w_1, xyz_u)) wu_dir_real = normalize_vector(subtract_vectors(xyz_u, xyz_w_1)) connector_u = mesh.attributes['connector'][size_uv] pin = mesh.attributes['pin'][size_uv] hole = mesh.attributes['hole'][size_uv] xyz_u_1 = add_vectors( xyz_u, scale_vector(uw_dir_real, 1e-3 * (0.5 * connector_u + 100))) xyz_u_2 = add_vectors(xyz_u, scale_vector(uw_dir_real, -1e-3 * 100)) xyz_v_1 = add_vectors( xyz_u, scale_vector(uw_dir_real, length_uv - 1e-3 * (80 + 4 + 5 + pin)))
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()