def test_add_vertex(): network = Network() assert network.add_vertex() == 0 assert network.add_vertex(x=0, y=0, z=0) == 1 assert network.add_vertex(key=2) == 2 assert network.add_vertex(key=0, x=1) == 0
print('max angle', min(angles.values())) print('min angle', max(angles.values())) # ========================================================================== # Create Face Adjacency Network - keys from 0 to N! # ========================================================================== n = mesh.number_of_faces() network = Network() for fkey in mesh.faces(): xyz = centroids[fkey] attr_dict = {k: v for k, v in zip('xyz', xyz)} network.add_vertex(key=fkey, attr_dict=attr_dict) for fkey in mesh.faces(): nbrs = mesh.face_neighbors(fkey) for nbr in nbrs: if fkey == nbr: continue angle_diff = math.fabs(angles[fkey] - angles[nbr]) try: ad = network.get_edge_attribute((fkey, nbr), 'angle_diff') if ad: continue except: network.add_edge(fkey, nbr, attr_dict={'angle_diff': angle_diff})
class Assembly(FromToData, FromToJson): """A data structure for discrete element assemblies. An assembly is essentially a network of assembly elements. Each element is represented by a vertex of the network. Each interface or connection between elements is represented by an edge of the network. Attributes ---------- network : :class:`compas.Network`, optional elements : list of :class:`Element`, optional A list of assembly elements. attributes : dict, optional User-defined attributes of the assembly. Built-in attributes are: * name (str) : ``'Assembly'`` default_element_attribute : dict, optional User-defined default attributes of the elements of the assembly. The built-in attributes are: * is_planned (bool) : ``False`` * is_placed (bool) : ``False`` default_connection_attributes : dict, optional User-defined default attributes of the connections of the assembly. Examples -------- >>> assembly = Assembly() >>> for i in range(2): >>> element = Element.from_box(Box(Frame.worldXY(), 10, 5, 2)) >>> assembly.add_element(element) """ def __init__(self, elements=None, attributes=None, default_element_attribute=None, default_connection_attributes=None): self.network = Network() self.network.attributes.update({'name': 'Assembly'}) if attributes is not None: self.network.attributes.update(attributes) self.network.default_vertex_attributes.update({ 'is_planned': False, 'is_placed': False }) if default_element_attribute is not None: self.network.default_vertex_attributes.update( default_element_attribute) if default_connection_attributes is not None: self.network.default_edge_attributes.update( default_connection_attributes) if elements: for element in elements: self.add_element(element) @property def name(self): """str : The name of the assembly.""" return self.network.attributes.get('name', None) @name.setter def name(self, value): self.network.attributes['name'] = value def number_of_elements(self): """Compute the number of elements of the assembly. Returns ------- int The number of elements. """ return self.network.number_of_vertices() def number_of_connections(self): """Compute the number of connections of the assembly. Returns ------- int the number of connections. """ return self.network.number_of_edges() @property def data(self): """Return a data dictionary of the assembly. """ # Network data does not recursively serialize to data... d = self.network.data # so we need to trigger that for elements stored in vertices vertex = {} for vkey, vdata in d['vertex'].items(): vertex[vkey] = { key: vdata[key] for key in vdata.keys() if key != 'element' } vertex[vkey]['element'] = vdata['element'].to_data() d['vertex'] = vertex return d @data.setter def data(self, data): # Deserialize elements from vertex dictionary for _vkey, vdata in data['vertex'].items(): vdata['element'] = Element.from_data(vdata['element']) self.network = Network.from_data(data) def clear(self): """Clear all the assembly data.""" self.network.clear() def add_element(self, element, key=None, attr_dict={}, **kwattr): """Add an element to the assembly. Parameters ---------- element : Element The element to add. attr_dict : dict, optional A dictionary of element attributes. Default is ``None``. Returns ------- hashable The identifier of the element. """ attr_dict.update(kwattr) x, y, z = element.frame.point key = self.network.add_vertex(key=key, attr_dict=attr_dict, x=x, y=y, z=z, element=element) return key def add_connection(self, u, v, attr_dict=None, **kwattr): """Add a connection between two elements and specify its attributes. Parameters ---------- u : hashable The identifier of the first element of the connection. v : hashable The identifier of the second element of the connection. attr_dict : dict, optional A dictionary of connection attributes. kwattr Other connection attributes as additional keyword arguments. Returns ------- tuple The identifiers of the elements. """ return self.network.add_edge(u, v, attr_dict, **kwattr) def transform(self, transformation): """Transforms this assembly. Parameters ---------- transformation : :class:`Transformation` Returns ------- None """ for _k, element in self.elements(data=False): element.transform(transformation) def transformed(self, transformation): """Returns a transformed copy of this assembly. Parameters ---------- transformation : :class:`Transformation` Returns ------- Assembly """ assembly = self.copy() assembly.transform(transformation) return assembly def copy(self): """Returns a copy of this assembly. """ raise NotImplementedError def element(self, key): """Get an element by its key.""" return self.network.vertex[key]['element'] def elements(self, data=False): """Iterate over the elements of the assembly. Parameters ---------- data : bool, optional If ``True``, yield both the identifier and the attributes. Yields ------ 2-tuple The next element as a (key, element) tuple, if ``data`` is ``False``. 3-tuple The next element as a (key, element, attr) tuple, if ``data`` is ``True``. """ if data: for vkey, vattr in self.network.vertices(True): yield vkey, vattr['element'], vattr else: for vkey in self.network.vertices(data): yield vkey, self.network.vertex[vkey]['element'] def connections(self, data=False): """Iterate over the connections of the network. Parameters ---------- data : bool, optional If ``True``, yield both the identifier and the attributes. Yields ------ 2-tuple The next connection identifier (u, v), if ``data`` is ``False``. 3-tuple The next connection as a (u, v, attr) tuple, if ``data`` is ``True``. """ return self.network.edges(data)
def egi_from_vectors(vectordict, origin, tol=0.001): """Construct an egi from a set of vectors. Parameters ---------- vectordict : dict A dectionary of key-vector pairs. origin : list The coordinates of the centroid. tol : float, optional Tolerance for evaluating antipodal. Returns ------- egi : mesh A mesh object representing the egi. Raises ------ Exception If there are less than four vectors. Notes ----- This algorithm is dependent on Rhinoceros objects; the adjacency arcs are implemented using Rhino.Geometry.Arc, and the cross-adjacencies (arc-arc intersections) are computed using Rhino.Geometry.Intersect.Intersection.CurveCurve. Warning ------- - This algorithm does not address scenarios where multiple parallel (collinear) vectors are present. References ---------- - Horn, B.K.P. (1984). *Extended Gaussian images*. - Moni, S. (1990, June). *A closed-form solution for the reconstruction of a convex polyhedron from its extended gaussian image.* - Lee, J., T. Van Mele, and P. Block (2018). *Disjointed force polyhedra.* """ if len(vectordict) < 4: raise Exception('Four or more vectors are needed for the construction of egi.') egi = Network() # -------------------------------------------------------------------------- # 1. add vertices from vectors # -------------------------------------------------------------------------- vertex_geokeys = {} for vkey in vectordict: normal = normalize_vector(vectordict[vkey]) vertex_xyz = add_vectors(normal, origin) vertex_geokeys[geometric_key(normal)] = vkey egi.add_vertex(x=vertex_xyz[0], y=vertex_xyz[1], z=vertex_xyz[2], key=vkey, attr_dict={'type' : 'face', 'normal': normal, 'nbrs' : []}) # -------------------------------------------------------------------------- # 2. Identify main adjacencies # -------------------------------------------------------------------------- vkey_pairs = set() for vkey in egi.vertex: v_crs_dict = {} for nbr_vkey in egi.vertex: if nbr_vkey is not vkey: n1 = egi.vertex[vkey]['normal'] n2 = egi.vertex[nbr_vkey]['normal'] # This checks if the normals are opposite ---------------------- dot = dot_vectors(n1, n2) if dot > 1 - tol: raise Exception("Coincident vectors detected.") elif dot > -1 + tol: this_crs = cross_vectors(n1, n2) unit_crs = normalize_vector(this_crs) crs_gkey = geometric_key(unit_crs) # Check to see if any other normals are coplanar if crs_gkey not in v_crs_dict: v_crs_dict[crs_gkey] = nbr_vkey # If multiple arcs are coplanar, choose the closer one elif crs_gkey in v_crs_dict: this_dist = distance(egi.vertex_coordinates(vkey), egi.vertex_coordinates(nbr_vkey)) test_dist = distance(egi.vertex_coordinates(vkey), egi.vertex_coordinates(v_crs_dict[crs_gkey])) if this_dist < test_dist: del v_crs_dict[crs_gkey] v_crs_dict[crs_gkey] = nbr_vkey # Add to overall connectivity dict ------------------------------------- for crs_gkey in v_crs_dict: nbr_vkey = v_crs_dict[crs_gkey] pair = frozenset([vkey, nbr_vkey]) vkey_pairs.add(pair) # -------------------------------------------------------------------------- # 3. Main adjacency arcs # -------------------------------------------------------------------------- arcs = {} for pair in vkey_pairs: u, v = list(pair) arc = _draw_arc(egi.vertex[u]['normal'], egi.vertex[v]['normal'], origin) if len(arcs) == 0: arc_key = 0 else: arc_key = max(int(x) for x in arcs.keys()) + 1 arcs[arc_key] = {'arc' : arc, 'vkeys' : [u, v], 'end_vkeys': [u, v], 'int_vkeys': {}, } # -------------------------------------------------------------------------- # 3. arc intersections --> cross adjacencies # -------------------------------------------------------------------------- arc_pairs_seen = set() for arckey_1 in arcs: for arckey_2 in arcs: if arckey_1 != arckey_2: arc_pair = frozenset([arckey_1, arckey_2]) if arc_pair not in arc_pairs_seen: arc_1 = arcs[arckey_1]['arc'] arc_2 = arcs[arckey_2]['arc'] intersection = _curve_curve_intx(arc_1, arc_2) if intersection: new_vkey = max(int(vkey) for vkey in egi.vertex.keys()) + 1 new_normal = subtract_vectors(intersection, origin) new_normal = normalize_vector(new_normal) new_vertex_geokey = geometric_key(new_normal, precision='3f') # if intersection is not an endpoint ------------------- if new_vertex_geokey not in vertex_geokeys.keys(): vertex_geokeys[new_vertex_geokey] = new_vkey egi.add_vertex(x=intersection[0], y=intersection[1], z=intersection[2], key=new_vkey, attr_dict={'type' : 'zero', 'normal' : new_normal, 'magnitude' : 0, 'nbrs' : []}) arcs[arckey_1]['vkeys'].append(new_vkey) arcs[arckey_2]['vkeys'].append(new_vkey) arcs[arckey_1]['int_vkeys'][new_vkey] = arckey_2 arcs[arckey_2]['int_vkeys'][new_vkey] = arckey_1 # if intersection already exists ----------------------- elif new_vertex_geokey in vertex_geokeys.keys(): vkey = vertex_geokeys[new_vertex_geokey] if vkey not in arcs[arckey_1]['vkeys']: arcs[arckey_1]['vkeys'].append(vkey) arcs[arckey_1]['int_vkeys'][vkey] = arckey_2 if vkey not in arcs[arckey_2]['vkeys']: arcs[arckey_2]['vkeys'].append(vkey) arcs[arckey_2]['int_vkeys'][vkey] = arckey_1 arc_pairs_seen.add(arc_pair) # -------------------------------------------------------------------------- # 5. Reorder vertices along each arc and add edges to EGI network # -------------------------------------------------------------------------- for arckey in arcs: vkeys = arcs[arckey]['vkeys'] if len(vkeys) > 2: pt_list = [egi.vertex_coordinates(key) for key in vkeys] arcs[arckey]['vkeys'] = _reorder_pts_on_arc(pt_list, arcs[arckey]['vkeys'], arcs[arckey]['arc'])[1] edge_type = 'cross' else: edge_type = 'main' for i in range(len(arcs[arckey]['vkeys']) - 1): vkey_1 = arcs[arckey]['vkeys'][i] vkey_2 = arcs[arckey]['vkeys'][i + 1] egi.vertex[vkey_1]['nbrs'] += [vkey_2] egi.vertex[vkey_2]['nbrs'] += [vkey_1] egi.add_edge(vkey_1, vkey_2) # -------------------------------------------------------------------------- # 6. For each vertex, sort nbrs in ccw order # -------------------------------------------------------------------------- _egi_sort_v_nbrs(egi) # -------------------------------------------------------------------------- # 7. Add EGI Network faces # -------------------------------------------------------------------------- egi_mesh = EGI() for vkey in egi.vertex: egi_mesh.vertex[vkey] = egi.vertex[vkey] egi_mesh.attributes['name'] = 'egi' egi_mesh.attributes['origin'] = list(origin) _egi_find_faces(egi, egi_mesh) return egi_mesh