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)