def test_astar_shortest_path(): n = Network() a = n.add_node(x=1, y=2, z=0) b = n.add_node(x=3, y=1, z=0) n.add_edge(a, b) path = astar_shortest_path(n, a, b) assert path == [a, b]
def test_astar_shortest_path_disconnected(): n = Network() a = n.add_node(x=1, y=0, z=0) b = n.add_node(x=2, y=0, z=0) c = n.add_node(x=3, y=0, z=0) n.add_edge(a, b) path = astar_shortest_path(n, a, c) assert path is None
def test_json_network(): before = Network() a = before.add_node() b = before.add_node() before.add_edge(a, b) after = compas.json_loads(compas.json_dumps(before)) assert before.dtype == after.dtype assert before.attributes == after.attributes assert all(before.has_node(node) for node in after.nodes()) assert all(after.has_node(node) for node in before.nodes()) assert all(before.has_edge(*edge) for edge in after.edges()) assert all(after.has_edge(*edge) for edge in before.edges())
def meshes_to_network(meshes): network = Network() network.update_default_node_attributes(mesh=None, vkey=None, fkey=None) network.update_default_edge_attributes(mesh=None, fkey=None) for i, mesh in enumerate(meshes): for vkey in mesh.vertices(): x, y, z = mesh.vertex_coordinates(vkey) network.add_node(x=x, y=y, z=z, mesh=i, vkey=vkey) for u, v in mesh.edges(): u1 = next(network.nodes_where({"vkey": u, "mesh": i})) v1 = next(network.nodes_where({"vkey": v, "mesh": i})) network.add_edge(u1, v1, mesh=i) return network
def test_astar_shortest_path_cycle(): n = Network() a = n.add_node(x=1, y=0, z=0) b = n.add_node(x=2, y=0, z=0) c = n.add_node(x=3, y=0, z=0) d = n.add_node(x=4, y=0, z=0) e = n.add_node(x=3.5, y=5, z=0) n.add_edge(a, b) n.add_edge(a, e) n.add_edge(b, c) n.add_edge(c, d) n.add_edge(e, d) path = astar_shortest_path(n, a, d) assert path == [a, b, c, d]
import random from compas_rhino.artists import NetworkArtist from compas.datastructures import Network network = Network() last_node = None for i in range(12): node = network.add_node(x=i // 3, y=i % 3, z=0) network.node_attribute(node, 'weight', random.choice(range(20))) if last_node: network.add_edge(node, i - 1) last_node = node print(network.summary()) print(network.to_data()) text = { node: network.node_attribute(node, 'weight') for node in network.nodes() } artist = NetworkArtist(network, layer='network') artist.clear_layer() artist.draw_nodelabels(text) artist.draw() artist.redraw()
# with 5 nodes and 4 edges network = Network() network.update_dna(is_anchor=False) network.update_dna(rx=0, ry=0, rz=0) network.update_dea(f=1) a = network.add_node(x=0, y=0, z=0, is_anchor=True) b = network.add_node(x=10, y=0, z=10, is_anchor=True) c = network.add_node(x=10, y=10, z=0, is_anchor=True) d = network.add_node(x=0, y=10, z=10, is_anchor=True) e = network.add_node(x=5, y=5, z=0) network.add_edge(a, e) network.add_edge(b, e) network.add_edge(c, e) network.add_edge(d, e) # compute all residuals in the current geometry for node in network.nodes(): r = compute_residual(network, node) network.node_attributes(node, ['rx', 'ry', 'rz'], r) # move free nodes in direction of residual for node in network.nodes(): if network.node_attribute(node, 'is_anchor'): continue
from compas.geometry import Pointcloud, KDTree from compas.datastructures import Network cloud = Pointcloud.from_bounds(10, 5, 3, 200) tree = KDTree(cloud) network = Network() for point in cloud: network.add_node(x=point[0], y=point[1], z=point[2]) for node in network.nodes(): point = network.node_coordinates(node) for nbr in tree.nearest_neighbors(point, 4, distance_sort=True): if nbr[2] < 1e-6: continue if not network.has_edge(node, nbr[1], directed=False): network.add_edge(node, nbr[1]) start = network.get_any_node() goal = network.get_any_node() path = network.shortest_path(start, goal)
def k5_network(): network = Network() network.add_edge('a', 'b') network.add_edge('a', 'c') network.add_edge('a', 'd') network.add_edge('a', 'e') network.add_edge('b', 'c') network.add_edge('b', 'd') network.add_edge('b', 'e') network.add_edge('c', 'd') network.add_edge('c', 'e') network.add_edge('d', 'e') return network
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 node 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_attributes=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_node_attributes.update({ 'is_planned': False, 'is_placed': False }) if default_element_attributes is not None: self.network.default_node_attributes.update( default_element_attributes) 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_nodes() 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 nodes node = {} for vkey, vdata in d['data']['node'].items(): node[vkey] = { key: vdata[key] for key in vdata.keys() if key != 'element' } node[vkey]['element'] = vdata['element'].to_data() d['data']['node'] = node return d @data.setter def data(self, data): # Deserialize elements from node dictionary for _vkey, vdata in data['data']['node'].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_node(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, data=False): """Get an element by its key.""" if data: return self.network.node[key]['element'], self.network.node[key] else: return self.network.node[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.nodes(True): yield vkey, vattr['element'], vattr else: for vkey in self.network.nodes(data): yield vkey, self.network.node[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)
class AMModel(FromToData, FromToJson): """A data structure for non-discrete element fabrication. A model is essentially a network of nodes. Each geometrical node is represented by a layer in fabrication. The sequence of layers in fabrication is represented by an edge of the network. Attributes ---------- network : :class:`compas.Network`, optional layers : list of :class:`Layer`, optional A list of model layers. attributes : dict, optional User-defined attributes of the model. Built-in attributes are: * name (str) : ``'AMModel'`` default_layer_attribute : dict, optional User-defined default attributes of the layers of the model. The built-in attributes are: * is_planned (bool) : ``False`` * is_placed (bool) : ``False`` Examples -------- """ def __init__(self, layers=None, attributes=None, default_layer_attribute=None, default_connection_attributes=None): self.network = Network() self.network.attributes.update({'name': 'AMModel'}) if attributes is not None: self.network.attributes.update(attributes) self.network.default_node_attributes.update({ 'is_planned': False, 'is_placed': False }) if default_layer_attribute is not None: self.network.default_node_attributes.update( default_layer_attribute) if default_connection_attributes is not None: self.network.default_edge_attributes.update( default_connection_attributes) if layers: for layer in layers: self.add_layer(layer) @property def name(self): """str : The name of the model.""" return self.network.attributes.get('name', None) @name.setter def name(self, value): self.network.attributes['name'] = value def number_of_layers(self): """Compute the number of layers of the model. Returns ------- int The number of nodes. """ return self.network.number_of_nodes() def number_of_connections(self): """Compute the number of connections of the model. Returns ------- int the number of connections. """ return self.network.number_of_edges() @property def data(self): """Return a data dictionary of the model. """ # Network data does not recursively serialize to data... d = self.network.data #print(d.keys()) # so we need to trigger that for layers stored in nodes node = {} for vkey, vdata in d['data']['node'].items(): node[vkey] = { key: vdata[key] for key in vdata.keys() if key != 'layer' } node[vkey]['layer'] = vdata['layer'].to_data() d['data']['node'] = node return d @data.setter def data(self, data): # Deserialize elements from node dictionary for _vkey, vdata in data['data']['node'].items(): vdata['layer'] = Layer.from_data(vdata['layer']) self.network = Network.from_data(data) def clear(self): """Clear all the model data.""" self.network.clear() def add_layer(self, layer, key=None, attr_dict={}, **kwattr): """Add an element to the model. Parameters ---------- layer : Layer The layer to add. attr_dict : dict, optional A dictionary of layer attributes. Default is ``None``. Returns ------- hashable The identifier of the element. """ attr_dict.update(kwattr) key = self.network.add_node(key=key, attr_dict=attr_dict, layer=layer) return key def add_edge(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 model. Parameters ---------- transformation : :class:`Transformation` Returns ------- None """ for _k, layer in self.layers(data=False): layer.transform(transformation) def transformed(self, transformation): """Returns a transformed copy of this model. Parameters ---------- transformation : :class:`Transformation` Returns ------- Assembly """ model = self.copy() model.transform(transformation) return model def copy(self): """Returns a copy of this model. """ layers = [] for key, layer in self.layers(): layers.append(layer.copy()) return AMModel(layers, self.network.attributes) def layer(self, key, data=False): """Get an layer by its key.""" if data: return self.network.node[key]['layer'], self.network.node[key] else: return self.network.node[key]['layer'] def layers(self, data=False): """Iterate over the elements of the model. 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.nodes(True): yield vkey, vattr['layer'], vattr else: for vkey in self.network.nodes(data): yield vkey, self.network.node[vkey]['layer'] 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)
import random from compas_rhino.artists import NetworkArtist from compas.datastructures import Network network = Network() network.add_edge(1, 2) network.add_edge(2, 3) network.add_edge(1, 4) network.add_edge(4, 5) network.add_edge(4, 6) for node in network.nodes(): x = random.choice(range(5)) y = random.choice(range(5)) z = random.choice(range(5)) network.node_attributes(node, 'xyz', [x, y, z]) print(network.summary()) text = {node: str(node) for node in network.nodes()} artist = NetworkArtist(network, layer='network') artist.clear_layer() artist.draw_nodelabels(text) artist.draw() artist.redraw()
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}) # # ========================================================================== # # color up # # ========================================================================== anglemax = max(network.get_edges_attribute('angle_diff')) print('angle diff max', anglemax) colors = {} for u, v, attr in network.edges(True): angle_diff = attr['angle_diff'] color = i_to_rgb(angle_diff / anglemax) colors[(u, v)] = color # # ==========================================================================
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
from compas.datastructures import Network HERE = os.path.dirname(__file__) FILE = os.path.join(HERE, 'clusters.json') network = Network() network.update_default_node_attributes({'cluster': None, 'base': False}) cloud = Pointcloud.from_bounds(10, 5, 3, 100) kmeans = KMeans(n_clusters=10, n_init=500, max_iter=100).fit(array(cloud, dtype=float)) clusters = {} for i, point in zip(kmeans.labels_, cloud): print(i) if i not in clusters: clusters[i] = [] clusters[i].append(point) for index in clusters: nodes = [] for point in clusters[index]: node = network.add_node(x=point[0], y=point[1], z=point[2], cluster=index) nodes.append(node) x, y, z = centroid_points(clusters[index]) base = network.add_node(x=x, y=y, z=z, cluster=index, base=True) for node in nodes: network.add_edge(base, node) network.to_json(FILE)
# create a network network = Network() network.update_dna(is_anchor=False) network.update_dna(rx=0, ry=0, rz=0) network.update_dea(f=1) a = network.add_node(x=0, y=0, z=0, is_anchor=True) b = network.add_node(x=10, y=0, z=10, is_anchor=True) c = network.add_node(x=10, y=10, z=0, is_anchor=True) d = network.add_node(x=0, y=10, z=10, is_anchor=True) e = network.add_node(x=5, y=5, z=0) network.add_edge(a, e, f=2) network.add_edge(b, e) network.add_edge(c, e) network.add_edge(d, e) # visualize dynamic process layer = "ITA20::L5::FormFinding" artist = NetworkArtist(network, layer=layer) kmax = 100 tol = 0.01 update_residuals(network)
network = Network() network.update_dna(is_anchor=False) network.update_dna(rx=0, ry=0, rz=0) network.update_dna(px=0, py=0, pz=0) network.update_dea(f=0, q=1) a = network.add_node(x=0, y=0, z=0, is_anchor=True) b = network.add_node(x=10, y=0, z=10, is_anchor=True) c = network.add_node(x=10, y=10, z=0, is_anchor=True) d = network.add_node(x=0, y=10, z=10, is_anchor=True) e = network.add_node(x=5, y=5, z=0) network.add_edge(a, e, q=random.randint(1, 10)) network.add_edge(b, e, q=random.randint(1, 10)) network.add_edge(c, e, q=random.randint(1, 10)) network.add_edge(d, e, q=random.randint(1, 10)) # numerical data n = network.number_of_nodes() node_index = {node: index for index, node in enumerate(network.nodes())} fixed = list(network.nodes_where({'is_anchor': True})) free = list(network.nodes_where({'is_anchor': False})) fixed[:] = [node_index[node] for node in fixed] free[:] = [node_index[node] for node in free]
network = Network() network.update_dna(is_anchor=False) network.update_dna(rx=0, ry=0, rz=0) network.update_dna(px=0, py=0, pz=0) network.update_dea(q=1) a = network.add_node(x=0, y=0, z=0, is_anchor=True) b = network.add_node(x=10, y=0, z=10, is_anchor=True) c = network.add_node(x=10, y=10, z=0, is_anchor=True) d = network.add_node(x=0, y=10, z=10, is_anchor=True) e = network.add_node(x=5, y=5, z=0) network.add_edge(a, e, q=2) network.add_edge(b, e, q=5) network.add_edge(c, e, q=3) network.add_edge(d, e) # numerical data n = network.number_of_nodes() e = network.number_of_edges() node_index = {node: index for index, node in enumerate(network.nodes())} fixed = list(network.nodes_where({'is_anchor': True})) free = list(network.nodes_where({'is_anchor': False})) fixed[:] = [node_index[node] for node in fixed]
from itertools import combinations from compas.datastructures import Network from compas.utilities import linspace, meshgrid from compas_rhino.artists import NetworkArtist X, Y = meshgrid(linspace(0, 10, 10), linspace(0, 5, 5)) points = [] for z in linspace(0, 3, 3): for xs, ys in zip(X, Y): for x, y in zip(xs, ys): points.append([x, y, z]) network = Network() for point in points: network.add_node(x=point[0], y=point[1], z=point[2]) for a, b in combinations(network.nodes(), 2): if network.node_attribute(a, 'z') != network.node_attribute(b, 'z'): network.add_edge(a, b) artist = NetworkArtist(network, layer="ITA20::Network") artist.clear_layer() artist.draw_nodes() artist.draw_edges()
class Layer: """Data structure representing a discrete set of nodes of an model. Attributes ---------- network : :class:`compas.Network`, optional nodes : :list:class:`Node`, optional Nodes discribing the layer geometry attributes : dict, optional User-defined attributes of the model. Built-in attributes are: * name (str) : ``'Layer'`` trajectory : :class:`compas_fab.robots.JointTrajectory` The robot trajectory in joint space path : :list: :class:`compas.geometry.Frame` The robot tool path in cartesian space Examples -------- """ def __init__(self, nodes=None, attributes=None, edges=None): self.network = Network() self.network.attributes.update({'name': 'Layer', 'is_constructed': False}) self.is_constructed = False if attributes is not None: self.network.attributes.update(attributes) if nodes: for node in nodes: self.add_node(node) self.trajectory = None @classmethod def from_nodes(cls, nodes): """Class method for constructing a layer from a Node objects. Parameters ---------- nodes :list: :class:`Node` List of Node objects. """ return cls(nodes) @property def name(self): """str : The name of the layer.""" return self.network.attributes.get('name', None) @name.setter def name(self, value): self.network.attributes['name'] = value def number_of_nodes(self): return self.network.number_of_nodes() def number_of_edges(self): return self.network.number_of_edges() def node(self, key, data=False): if data: return self.network.node[key]['node'], self.network.node[key] else: return self.network.node[key]['node'] def nodes(self, data=False): if data: for vkey, vattr in self.network.nodes(True): yield vkey, vattr['node'], vattr else: for vkey in self.network.nodes(data): yield vkey, self.network.node[vkey]['node'] def edges(self, data=False): return self.network.edges(data) @property def path(self): return [node.frame for key, node in self.nodes(False)] @path.setter def path(self, p): self.__path = p @classmethod def from_data(cls, data): """Construct an layer from its data representation. Parameters ---------- data : :obj:`dict` The data dictionary. Returns ------- Layer The constructed layer. """ layer = cls() layer.data = data return layer def to_data(self): """ docstring """ return self.data @property def data(self): """Returns the data dictionary that represents the layer. Returns ------- dict The layer data. Examples -------- >>> layer = Layer() >>> print(layer.data) """ d = self.network.data node = {} for vkey, vdata in d['data']['node'].items(): node[vkey] = {key: vdata[key] for key in vdata.keys() if key != 'node'} node[vkey]['node'] = vdata['node'].to_data() d['data']['node'] = node d['data']['is_constructed'] = self.is_constructed if self.trajectory: d['data']['attributes']['trajectory'] = [f.to_data() for f in self.trajectory] if self.path: d['data']['attributes']['path'] = [f.to_data() for f in self.path] return d @data.setter def data(self, data): for _vkey, vdata in data['data']['node'].items(): vdata['node'] = Node.from_data(vdata['node']) if 'is_constructed' in data: self.is_constructed = _deserialize_from_data(data['data']['attributes']['is_constructed']) if 'trajectory' in data: self.trajectory = _deserialize_from_data(data['data']['attributes']['trajectory']) if 'path' in data: self.path = [Frame.from_data(d) for d in data['data']['attributes']['path']] self.network = Network.from_data(data) def add_node(self, node, key=None, attr_dict={}, **kwattr): attr_dict.update(kwattr) key = self.network.add_node(key=key, attr_dict=attr_dict, node=node) return key def add_edge(self, u, v, attr_dict=None, **kwattr): return self.network.add_edge(u, v, attr_dict, **kwattr) def transform(self, transformation): for key, node in self.nodes(data=False): node.transform(transformation) def transformed(self, transformation): layer = self.copy() layer.transform(transformation) return layer def copy(self): """Returns a copy of this layer. Returns ------- Layer """ nodes = [] for key, node in self.nodes(): nodes.append(node.copy()) return Layer(nodes, self.network.attributes)