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)
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] free[:] = [node_index[node] for node in free] edges = [(node_index[u], node_index[v]) for u, v in network.edges()] X = network.nodes_attributes('xyz') R = network.nodes_attributes(['rx', 'ry', 'rz']) P = network.nodes_attributes(['px', 'py', 'pz']) Q = network.edges_attribute('q')
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)