def smooth_network_length(network, lmin, lmax, fixed=None, kmax=1, d=0.5, callback=None, callback_args=None): fixed = fixed or [] fixed = set(fixed) if callback: if not callable(callback): raise Exception('The callback is not callable.') for k in range(kmax): key_xyz = {key: network.vertex_coordinates(key) for key in network.vertices()} for key in network: if key in fixed: continue ep = key_xyz[key] points = [] for nbr in network.vertex_neighbours(key): sp = key_xyz[nbr] vec = subtract_vectors(ep, sp) lvec = length_vector(vec) scale = max(lmin, min(lvec, lmax)) p = add_vectors(sp, scale_vector(vec, scale / lvec)) points.append(p) x, y, z = centroid_points(points) attr = network.vertex[key] attr['x'] += d * (x - ep[0]) attr['y'] += d * (y - ep[1]) attr['z'] += d * (z - ep[2]) if callback: callback(network, k, callback_args)
def draw_cylinders(cylinders: List[Dict], collection: Union[Text, bpy.types.Collection] = None, uv: int = 10) -> List[bpy.types.Object]: """Draw cylinder objects as mesh primitives.""" from math import acos from math import atan2 bpy.ops.mesh.primitive_cylinder_add(location=[0, 0, 0], radius=1, depth=1, vertices=uv) empty = bpy.context.object _link_object(empty, collection) objects = [0] * len(cylinders) for index, data in enumerate(cylinders): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) radius = data.get('radius', 1.0) length = distance_point_point(sp, ep) obj = empty.copy() obj.name = data.get('name', 'cylinder') obj.rotation_euler[1] = acos((ep[2] - sp[2]) / length) obj.rotation_euler[2] = atan2(ep[1] - sp[1], ep[0] - sp[0]) obj.location = mp obj.scale = ((radius, radius, length)) rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) empty.hide_set(True) return objects
def vertex_normal(self, vertex): """Return the normal vector at the vertex as the weighted average of the normals of the neighboring faces. Parameters ---------- key : int The identifier of the vertex. Returns ------- list The components of the normal vector. """ if not self.is_vertex_on_boundary(vertex): return halffaces = [] for halfface in self.vertex_halffaces(vertex): if self.is_halfface_on_boundary(halfface): halffaces.append(halfface) vectors = [ self.face_normal(halfface, False) for halfface in halffaces if halfface is not None ] return normalize_vector(centroid_points(vectors))
def _face_adjacency(xyz, faces, nmax=10, radius=2.0): points = [centroid_points([xyz[index] for index in face]) for face in faces] k = min(len(faces), nmax) tree = cKDTree(points) _, closest = tree.query(points, k=k, n_jobs=-1) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() nnbrs = set(closest[face]) for u, v in pairwise(vertices + vertices[0:1]): for nbr in nnbrs: if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def kagome_polyedge_colouring(kagome): polyedges = kagome.polyedge_data edge_to_polyedge_index = {vkey: {} for vkey in kagome.vertices()} for i, polyedge in enumerate(polyedges): for u, v in pairwise(polyedge): edge_to_polyedge_index[u][v] = i edge_to_polyedge_index[v][u] = i vertices = [ centroid_points([kagome.vertex_coordinates(vkey) for vkey in polyedge]) for polyedge in polyedges ] edges = [] for idx, polyedge in enumerate(polyedges): for vkey in polyedge: for vkey_2 in kagome.vertex_neighbors(vkey): idx_2 = edge_to_polyedge_index[vkey][vkey_2] if idx_2 != idx and idx < idx_2 and (idx, idx_2) not in edges: edges.append((idx, idx_2)) polyedge_network = Network.from_nodes_and_edges(vertices, edges) key_to_colour = vertex_coloring(polyedge_network.adjacency) return [key_to_colour[key] for key in sorted(key_to_colour.keys())]
def check_element_exists(self, nodes=None, xyz=None, virtual=False): """ Check if an element already exists based on nodes or centroid. Parameters ---------- nodes : list Node numbers the element is connected to. xyz : list Direct co-ordinates of the element centroid to check. virtual: bool Is the element to be checked a virtual element. Returns ------- int The element index if the element already exists, None if not. Notes ----- - Geometric key check is made according to self.tol [m] tolerance. """ if not xyz: xyz = centroid_points([self.node_xyz(node) for node in nodes]) gkey = geometric_key(xyz, '{0}f'.format(self.tol)) if virtual: return self.virtual_element_index.get(gkey, None) else: return self.element_index.get(gkey, None)
def draw_edgelabels(self, text=None, color=None): """Draw labels for a selection of edges. Parameters ---------- text : dict, optional A dictionary of edge labels as edge-text pairs. The default value is ``None``, in which case every edge will be labelled with its key. color : tuple or dict of tuple, optional The color specification of the labels. The default color is the same as the default color for edges. Returns ------- list The GUIDs of the created Rhino objects. """ if text is None: edge_text = {(u, v): "{}-{}".format(u, v) for u, v in self.edges} elif isinstance(text, dict): edge_text = text else: raise NotImplementedError vertex_xyz = self.vertex_xyz edge_color = colordict(color, edge_text.keys(), default=self.default_edgecolor) labels = [] for edge in edge_text: labels.append({ 'pos': centroid_points([vertex_xyz[edge[0]], vertex_xyz[edge[1]]]), 'name': "{}.edgelabel.{}-{}".format(self.mesh.name, *edge), 'color': edge_color[edge], 'text': edge_text[edge] }) return compas_rhino.draw_labels(labels, layer=self.layer, clear=False, redraw=False)
def draw_lines(lines, layer=None, centroid=True): objects = [0] * len(lines) for index, data in enumerate(lines): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) if centroid else [0, 0, 0] name = data.get('name', 'line') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' spline = curve.splines.new('NURBS') spline.points.add(2) spline.points[0].co = list(subtract_vectors(sp, mp)) + [1] spline.points[1].co = list(subtract_vectors(ep, mp)) + [1] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = mp obj.data.fill_mode = 'FULL' obj.data.bevel_depth = data.get('width', 0.05) obj.data.bevel_resolution = 0 obj.data.resolution_u = 20 rgb = data.get('color') or [1.0, 1.0, 1.0] set_object_color(obj, rgb) objects[index] = obj link_objects(objects, layer=layer) return objects
def draw_cylinders(cylinders, div=10, layer=None): # don't set the obvious defaults? bpy.ops.mesh.primitive_cylinder_add(location=[0, 0, 0], radius=1, depth=1, vertices=div) empty = bpy.context.active_object objects = [0] * len(cylinders) for index, data in enumerate(cylinders): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) radius = data.get('radius', 1.0) length = distance_point_point(sp, ep) obj = empty.copy() obj.name = data.get('name', 'cylinder') # get this from geometry package obj.rotation_euler[1] = acos((ep[2] - sp[2]) / length) obj.rotation_euler[2] = atan2(ep[1] - sp[1], ep[0] - sp[0]) obj.location = mp obj.scale = ((radius, radius, length)) rgb = data.get('color') or [1.0, 1.0, 1.0] set_object_color(obj, rgb) objects[index] = obj delete_object(empty) link_objects(objects, layer=layer) return objects
def draw_line(start=[0, 0, 0], end=[1, 1, 1], width=0.05, centroid=True, name='line', color=[1, 1, 1], layer=None): mp = centroid_points([start, end]) if centroid else [0, 0, 0] curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' object = bpy.data.objects.new(name, curve) object.location = mp spline = curve.splines.new('NURBS') spline.points.add(2) spline.points[0].co = list(subtract_vectors(start, mp)) + [1] spline.points[1].co = list(subtract_vectors(end, mp)) + [1] spline.order_u = 1 object.data.fill_mode = 'FULL' object.data.bevel_depth = width object.data.bevel_resolution = 0 object.data.resolution_u = 20 object.data.materials.append(create_material(color=color)) bpy.context.collection.objects.link(object) if layer: set_objects_layer(objects=[object], layer=layer) return object
def from_planes(cls, planes): """Construct a polyhedron from intersecting planes. Parameters ---------- planes : list of :class:`compas.geometry.Plane` or list of (point, normal) Returns ------- :class:`compas.geometry.Polyhedron` Examples -------- >>> from compas.geometry import Plane >>> left = Plane([-1, 0, 0], [-1, 0, 0]) >>> right = Plane([+1, 0, 0], [+1, 0, 0]) >>> top = Plane([0, 0, +1], [0, 0, +1]) >>> bottom = Plane([0, 0, -1], [0, 0, -1]) >>> front = Plane([0, -1, 0], [0, -1, 0]) >>> back = Plane([0, +1, 0], [0, +1, 0]) >>> p = Polyhedron.from_planes([left, right, top, bottom, front, back]) """ from compas.geometry import Plane from compas.geometry import centroid_points planes = [Plane(point, normal) for point, normal in planes] interior = centroid_points([plane.point for plane in planes]) return cls.from_halfspaces([plane.abcd for plane in planes], interior)
def xdraw_pipes(pipes, div=8): """ Draw a set of pipes. Parameters: pipes (list): {'radius':, 'start':, 'end':, 'color':, 'name':, 'layer':}. div (int): Divisions around cross-section. Returns: list: Created pipe objects. """ objects = [] bpy.ops.mesh.primitive_cylinder_add(radius=1, depth=1, vertices=div, location=[0, 0, 0]) object = bpy.context.object for pipe in pipes: radius = pipe.get('radius', 1) start = pipe.get('start', [0, 0, 0]) end = pipe.get('end', [0, 0, 1]) L = distance_point_point(start, end) pos = centroid_points([start, end]) copy = object.copy() copy.name = pipe.get('name', 'pipe') copy.rotation_euler[1] = acos((end[2] - start[2]) / L) copy.rotation_euler[2] = atan2(end[1] - start[1], end[0] - start[0]) copy.location = Vector(pos) copy.data = copy.data.copy() copy.scale = ((radius, radius, L)) copy.show_wire = True copy.data.materials.append(bpy.data.materials[pipe.get('color', 'white')]) set_objects_layer([copy], pipe.get('layer', 0)) objects.append(copy) delete_objects([object]) for object in objects: bpy.context.scene.objects.link(object) deselect_all_objects() return objects
def interface(self, interface): self._interface = interface faces = [] xyz = interface['interface_points'] f = len(xyz) if f < 3: pass elif f == 3: faces.append([0, 1, 2]) elif f == 4: faces.append([0, 1, 2]) faces.append([2, 3, 0]) else: c = centroid_points(xyz) xyz.append(c) for a, b in pairwise(list(range(0, f))): faces.append([a, b, f]) faces.append([b, 0, f]) self._xyz = xyz self._faces = faces
def structure_face_centers(structure): eks = radiating_faces(structure) centers = [] for ek in eks: pl = [structure.nodes[nk].xyz() for nk in structure.elements[ek].nodes] centers.append(centroid_points(pl)) return centers
def draw_facenormals(self, faces=None, color=(0, 255, 255), scale=1.0): """Draw the normals of the faces. Parameters ---------- faces : list, optional A selection of face normals to draw. Default is to draw all face normals. color : tuple, optional The color specification of the normal vectors. The default color is cyan, ``(0, 255, 255)``. scale : float, optional Scale factor for the face normals. Default is ``1.0``. Returns ------- list The GUIDs of the created Rhino objects. """ vertex_xyz = self.vertex_xyz faces = faces or list(self.mesh.faces()) lines = [] for face in faces: a = centroid_points([vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]) n = self.mesh.face_normal(face) b = add_vectors(a, scale_vector(n, scale)) lines.append({ 'start': a, 'end': b, 'name': "{}.facenormal.{}".format(self.mesh.name, face), 'color': color, 'arrow': 'end'}) return compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
def polygon_normal_oriented(polygon, unitized=True): """Compute the oriented normal of any closed polygon (can be convex, concave or complex). Parameters ---------- polygon : sequence The XYZ 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 ------- list The weighted or unitized normal vector of the polygon. """ p = len(polygon) assert p > 2, "At least three points required" w = centroid_points(polygon) normal_sum = (0, 0, 0) for i in range(-1, len(polygon) - 1): u = polygon[i] v = polygon[i + 1] uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) normal = scale_vector(cross_vectors(uv, vw), 0.5) normal_sum = add_vectors(normal_sum, normal) if not unitized: return normal_sum return normalize_vector(normal_sum)
def add_element_to_element_index(self, key, nodes, virtual=False): """ Adds the element to the element_index dictionary. Parameters ---------- key : int Prescribed element key. nodes : list Node numbers the element is connected to. virtual: bool If true, adds element to the virtual_element_index dictionary. Returns ------- None """ centroid = centroid_points([self.node_xyz(node) for node in nodes]) gkey = geometric_key(centroid, '{0}f'.format(self.tol)) if virtual: self.virtual_element_index[gkey] = key else: self.element_index[gkey] = key
def polygon_area_footprint(polygon): """Compute the non-oriented area of a polygon (can be convex or concave). Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- float The non-oriented area of the polygon. """ area = 0 w = centroid_points(polygon) for i in range(-1, len(polygon) - 1): u = polygon[i] v = polygon[i + 1] uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) normal = scale_vector(cross_vectors(uv, vw), 0.5) area += length_vector(normal) return area
def center_assembly(self): xyz = self.assembly.get_vertices_attributes('xyz') cx, cy, cz = centroid_points(xyz) for key, attr in self.assembly.vertices(True): attr['x'] -= cx attr['y'] -= cy attr['z'] -= cz
def draw_edgelabels(self, text: Optional[Dict[Tuple[int, int], str]] = None, color: Optional[Union[str, RGBColor, List[RGBColor], Dict[int, RGBColor]]] = None ) -> List[bpy.types.Object]: """Draw labels for a selection of edges. Parameters ---------- text : dict, optional A dictionary of edge labels as edge-text pairs. The default value is ``None``, in which case every edge will be labeled with its key. color : rgb-tuple or dict of rgb-tuple The color specification of the labels. The default color is the same as the default color for edges. Returns ------- list of :class:`bpy.types.Object` """ if text is None: edge_text = {(u, v): "{}-{}".format(u, v) for u, v in self.edges} elif isinstance(text, dict): edge_text = text else: raise NotImplementedError edge_color = colordict(color, edge_text, default=self.default_edgecolor) labels = [] for edge in edge_text: labels.append({ 'pos': centroid_points([self.node_xyz[edge[0]], self.node_xyz[edge[1]]]), 'name': "{}.edgelabel.{}-{}".format(self.network.name, *edge), 'text': edge_text[edge] }) return compas_blender.draw_texts(labels, collection=self.edgelabelcollection, color=edge_color)
def radiating_face_centers(self): eks = self.radiating_faces() centers = [] for ek in eks: pl = [self.nodes[nk].xyz() for nk in self.elements[ek].nodes] centers.append(centroid_points(pl)) return centers
def intersect_lines_colinear(l1, l2, tol): def are_segments_colinear(l1, l2, tol): a, b = l1 d, c = l2 return is_colinear(a, b, c, tol) if are_segments_colinear(l1, l2, tol): return centroid_points([l1[1], l2[0]])
def planarize_mesh(mesh, fixed=None, kmax=100, d=1.0, callback=None, callback_args=None): """Planarise the faces of a mesh. Planarisation is implemented as a two-step iterative procedure. At every iteration, faces are first individually projected to their best-fit plane, and then the vertices are projected to the centroid of the disconnected corners of the faces. Parameters: mesh fixed kmax d callback callback_args Returns: None """ # planarize every face individually # by projecting all vertices onto the best-fit plane # reconnect the corners of the faces # by mapping the vertices to the centroids of the face corners if callback: if not callable(callback): raise Exception('The callback is not callable.') fixed = fixed or [] fixed = set(fixed) for k in range(kmax): key_xyz = {key: [] for key in mesh.vertices()} for fkey in mesh.faces(): points = mesh.face_coordinates(fkey) plane = bestfit_plane_from_points(points) projections = project_points_plane(points, plane) for index, key in enumerate(mesh.face_vertices(fkey)): key_xyz[key].append(projections[index]) for key, attr in mesh.vertices(data=True): if key in fixed: continue x, y, z = centroid_points(key_xyz[key]) attr['x'] = x attr['y'] = y attr['z'] = z if callback: callback(mesh, k, callback_args)
def _face_adjacency(xyz, faces, nmax=10, radius=2.0): points = [centroid_points([xyz[index] for index in face]) for face in faces] tree = KDTree(points) closest = [tree.nearest_neighbors(point, nmax) for point in points] closest = [[index for _, index, _ in nnbrs] for nnbrs in closest] adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() nnbrs = set(closest[face]) for u, v in pairwise(vertices + vertices[0:1]): for nbr in nnbrs: if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def draw_lines(lines: List[Dict], collection: Union[Text, bpy.types.Collection] = None, centroid: bool = True) -> List[bpy.types.Object]: """Draw line objects.""" L = len(lines) N = len(str(L)) objects = [0] * L for index, data in enumerate(lines): sp = data['start'] ep = data['end'] origin = centroid_points([sp, ep]) if centroid else [0, 0, 0] name = data.get('name', f'L.{index:0{N}d}') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' spline = curve.splines.new('POLY') spline.points.add(1) spline.points[0].co = subtract_vectors(sp, origin) + [1.0] spline.points[1].co = subtract_vectors(ep, origin) + [1.0] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = origin obj.data.fill_mode = 'FULL' obj.data.bevel_depth = data.get('width', 0.05) obj.data.bevel_resolution = 0 obj.data.resolution_u = 20 rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) return objects
def smooth_mesh_centroid(mesh, fixed=None, kmax=1, d=1.0, callback=None, callback_args=None): """""" if callback: if not callable(callback): raise Exception('Callback is not callable.') fixed = fixed or [] fixed = set(fixed) for k in range(kmax): key_xyz = {key: mesh.vertex_coordinates(key) for key in mesh} for key in mesh.vertices(): if key in fixed: continue p = key_xyz[key] nbrs = mesh.vertex_neighbours(key) c = centroid_points([key_xyz[nbr] for nbr in nbrs]) # update attr = mesh.vertex[key] attr['x'] += d * (c[0] - p[0]) attr['y'] += d * (c[1] - p[1]) attr['z'] += d * (c[2] - p[2]) if callback: callback(mesh, k, callback_args)
def draw_pipes(pipes: List[Dict], collection: Union[Text, bpy.types.Collection] = None, centroid: bool = True, smooth: bool = True) -> List[bpy.types.Object]: """Draw polyline objects.""" P = len(pipes) N = len(str(P)) objects = [0] * P for index, data in enumerate(pipes): points = data['points'] origin = centroid_points(points) if centroid else [0, 0, 0] name = data.get('name', f'POLY.{index:0{N}d}') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' curve.fill_mode = 'FULL' curve.bevel_depth = data.get('width', 0.05) curve.bevel_resolution = 0 curve.resolution_u = 20 curve.use_fill_caps = True if smooth: spline = curve.splines.new('NURBS') else: spline = curve.splines.new('POLY') spline.points.add(len(points) - 1) for i, point in enumerate(points): spline.points[i].co = subtract_vectors(point, origin) + [1.0] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = origin rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) return objects
def center_mesh(self): xyz = [self.mesh.vertex_coordinates(key) for key in self.mesh.vertices()] cx, cy, cz = centroid_points(xyz) for key, attr in self.mesh.vertices(True): attr['x'] -= cx attr['y'] -= cy attr['z'] -= cz
def check_element_exists(self, nodes, xyz=None): """ Check if an element already exists based on the nodes it connects to or its centroid. Parameters ---------- nodes : list Node numbers the element is connected to. xyz : list Direct co-ordinates of the element centroid to check. Returns ------- int The element index if the element already exists, None if not. Notes ----- - Geometric key check is made according to self.tol [m] tolerance. """ if not xyz: xyz = centroid_points([self.node_xyz(node) for node in nodes]) gkey = geometric_key(xyz, '{0}f'.format(self.tol)) return self.element_index.get(gkey, None)
def smooth_network_centroid(network, fixed=None, kmax=1, d=0.5, callback=None, callback_args=None): """Smooth a network using per vertex the centroid of its neighbours. Parameters: network (compas.datastructures.network.Network): The network object. fixed (list): Optional. The fixed vertices of the network. Default is ``None``. kmax (int): Optional. The maximum number of iterations. Default is ``1``. d (float): Optional. The damping factor. Default is ``0.5``. callback (callable): Optional. A user-defined callback function to be executed after every iteration. Default is ``None``. Raises: Exception: If a callback is provided, but not callable. Example: .. plot:: :include-source: import compas from compas.datastructures.network import Network from compas.datastructures.network.algorithms import smooth_network_centroid network = Network.from_obj(compas.get_data('grid_irregular.obj')) smooth_network_centroid(network, fixed=network.leaves(), kmax=10) network.plot() """ fixed = fixed or [] fixed = set(fixed) if callback: if not callable(callback): raise Exception('The callback is not callable.') for k in range(kmax): key_xyz = {key: network.vertex_coordinates(key) for key in network.vertices()} for key in network: if key in fixed: continue nbrs = network.vertex_neighbours(key) points = [key_xyz[nbr] for nbr in nbrs] cx, cy, cz = centroid_points(points) x, y, z = key_xyz[key] attr = network.vertex[key] attr['x'] += d * (cx - x) attr['y'] += d * (cy - y) attr['z'] += d * (cz - z) if callback: callback(network, k, callback_args)