diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f231712..9786d206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,24 +13,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [0.1.0] 2023-12-06 + +### Added + +### Changed +* Changed `all` general docstring formatting issues +* Changed `compas_model.model.element_node.ElementNode` property `my_object` is changed to `Element` +* Changed `compas_model.model.group_node.GroupNode` property `my_object` is changed to `geometry` +* Changed `compas_model.model.element_tree.ElementTree.` serialization method `from_data` check if node has not children and creates as a result different node type, instead of `my_object` +* Changed `test_model` function `create_model` by adding more nodes at leaves to really check if the serialization works. + +### Removed ## [0.1.0] 2023-12-05 ### Added -* TEST_PULL_REQUESTS(LICENSE) change to MIT license -* TEST_PULL_REQUESTS(requirements-dev.txt) added shapely library -* TEST_PULL_REQUESTS(readme.md) remove emtpy spaces - -* DATA_STRUCTURE_MODEL(src\compas_model\model\group_node.py) create a group node that has an attribute of _my_object that is stored in the node Attributes, check the serialization, there must be a property _my_object that refers to the attribute. Try this class from the compas2 Tree implementation. -* DATA_STRUCTURE_MODEL(src\compas_model\model\element_node.py) same description as for the group_node, but this type the class works with elements only -* DATA_STRUCTURE_MODEL(src\compas_model\model\model.py) bring the code from the assembly -* DATA_STRUCTURE_MODEL(src\compas_model\model\element_tree.py) bring the code from the assembly -* DATA_STRUCTURE_MODEL(src\compas_model\model\tree_util.py) bring the composition functionality -* DATA_STRUCTURE_MODEL(all) remove all the additional methods -* DATA_STRUCTURE_MODEL(all) add the rest of the methods to work with data trees. -* PULL_REQUEST +* Added `requirements-dev` added shapely library +* Added `compas_model.model.group_node.GroupNode` create a group node that has an attribute of _my_object that is stored in the node Attributes, check the serialization, there must be a property _my_object that refers to the attribute. Try this class from the compas2 Tree implementation. +* Added `compas_model.model.group_node.ElementNode` same description as for the group_node, but this type the class works with elements only +* Added `compas_model.model.model.Model` bring the code from the assembly +* Added `compas_model.model.element_tree.ElementTree` bring the code from the assembly ### Changed +* Changed `README` remove empty spaces +* Changed `LICENSE` made MIT license ### Removed diff --git a/docs/api.rst b/docs/api.rst index 6b179dbb..e62a8e0e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,5 +10,3 @@ Packages :titlesonly: api/compas_model - api/compas_model.elements - api/compas_model.model diff --git a/src/compas_model/elements/element.py b/src/compas_model/elements/element.py index 2547a2ac..4aa5358c 100644 --- a/src/compas_model/elements/element.py +++ b/src/compas_model/elements/element.py @@ -79,6 +79,7 @@ class Element(Data): frame=Frame([3, 0, 0], [0.866, 0.1, 0.0], [0.5, 0.866, 0.0]), \ xsize=2, ysize=4, zsize=0.25), \ [Vector(0, 0, 1), 0]) + """ def __init__( @@ -372,7 +373,6 @@ def from_data(cls, data): @classmethod def from_geometry_simplified_and_geometry(cls, geometry_simplified, geometry=None): """convert a geometry to element, when indexing and types are not important""" - if geometry is not None: return Element(geometry_simplified=geometry_simplified, geometry=geometry) else: @@ -472,7 +472,6 @@ def from_plate_planes( cls, base_plane, side_planes, thickness, id=None, insertion=None ): """method create a plate element at the origin point with the frame at worldXY""" - # -------------------------------------------------------------------------- # intersect side planes with base plane to get a polyline) # -------------------------------------------------------------------------- @@ -505,7 +504,6 @@ def from_plate_planes( @property def fabrication(self): """Fabrication information e.g. subtractive, additive, nesting and etc""" - # define this property dynamically in the class if not hasattr(self, "_fabrication"): self._fabrication = {} @@ -514,7 +512,6 @@ def fabrication(self): @property def structure(self): """Structure information e.g. force vectors, minimal representation and etc""" - # define this property dynamically in the class if not hasattr(self, "_structure"): self._structure = {} @@ -526,9 +523,11 @@ def structure(self): @property def key(self): """Key: guid of the Element object stored in the base Node class attributes dictionary "my_object" property + Returns ------- str + """ return str(self.guid) @@ -579,7 +578,6 @@ def surface_area(self): float """ - # -------------------------------------------------------------------------- # sanity check # -------------------------------------------------------------------------- @@ -650,7 +648,6 @@ def center_of_mass(self): :class:`compas.geometry.Point` """ - # -------------------------------------------------------------------------- # sanity check # -------------------------------------------------------------------------- @@ -698,7 +695,6 @@ def centroid(self): :class:`compas.geometry.Point` """ - # -------------------------------------------------------------------------- # sanity check # -------------------------------------------------------------------------- @@ -730,7 +726,6 @@ def centroid(self): @property def frame_global(self): """Frame that gives orientation of the element in the larger group of Elements""" - # define this property dynamically in the class if not hasattr(self, "_frame_global"): self._frame_global = Frame([0, 0, 0], [1, 0, 0], [0, 1, 0]) @@ -862,7 +857,6 @@ def aabb(self, inflate=0.00): def oobb(self, inflate=0.00): """Compute the oriented bounding box based on geometry geometries points""" - # define this property dynamically in the class if not hasattr(self, "_oobb"): self._oobb = [] # XYZ coordinates of 8 points defining a box @@ -951,7 +945,6 @@ def aabb_center(self, inflate=0.001): @property def convex_hull(self): """Compute convex hull from geometry points""" - # define this property dynamically in the class if not hasattr(self, "_convex_hull"): self._convex_hull = Mesh() @@ -996,7 +989,6 @@ def has_collision(self, other): this function is often intermediate between high-performance tree searches then this collision is computed and then the interface can be found""" - # -------------------------------------------------------------------------- # sanity check # -------------------------------------------------------------------------- @@ -1082,7 +1074,6 @@ def GetSeparatingPlane(RPos, axis, box1, box2): def face_polygons(self): """Get Polygons from the geometry WARNING: currently the face polygons do not consider elements with holes""" - # -------------------------------------------------------------------------- # sanity check # -------------------------------------------------------------------------- @@ -1313,16 +1304,17 @@ def copy(self): # ========================================================================== def transform(self, transformation): - """ - Transforms the geometry , local frame, and global frame of the Element. + """Transforms the geometry, local frame, and global frame of the Element. - Parameters: + Parameters + ---------- transformation (Transformation): The transformation to be applied to the Element's geometry and frames. - Returns: + Returns + ------- None - """ + """ # transorm the geometry self.frame.transform(transformation) self.frame_global.transform(transformation) @@ -1350,68 +1342,78 @@ def transform(self, transformation): self.convex_hull.transform(transformation) def transformed(self, transformation): - """ - Creates a transformed copy of the Element. + """Creates a transformed copy of the Element. - Parameters: + Parameters + ---------- transformation (Transformation): The transformation to be applied to the copy. - Returns: + Returns + ------- Element: A new instance of the Element with the specified transformation applied. + """ new_instance = self.copy() new_instance.transform(transformation) return new_instance def transform_to_frame(self, frame): - """ - Applies frame_to_frame transformation to the geometry , local frame, and global frame of the Element. + """Applies frame_to_frame transformation to the geometry , local frame, and global frame of the Element. - Parameters: + Parameters + ---------- frame (Frame): The target frame to which the Element will be transformed. - Returns: + Returns + ------- None + """ xform = Transformation.from_frame_to_frame(self.frame, frame) self.transform(xform) def transform_from_frame_to_frame(self, source_frame, target_frame): - """ - Applies frame_to_frame transformation to the geometry , local frame, and global frame of the Element. + """Applies frame_to_frame transformation to the geometry , local frame, and global frame of the Element. - Parameters: + Parameters + ---------- frame (Frame): The target frame to which the Element will be transformed. - Returns: + Returns + ------- None + """ xform = Transformation.from_frame_to_frame(source_frame, target_frame) self.transform(xform) def transformed_to_frame(self, frame): - """ - Creates an oriented copy of the Element. + """Creates an oriented copy of the Element. - Parameters: + Parameters + ---------- frame (Frame): The target frame to which the Element will be transformed. - Returns: + Returns + ------- Element: A new instance of the Element with the specified orientation applied. + """ new_instance = self.copy() new_instance.transform_to_frame(frame) return new_instance def transformed_from_frame_to_frame(self, source_frame, target_frame): - """ - Creates an oriented copy of the Element. + """Creates an oriented copy of the Element. - Parameters: + Parameters + ---------- frame (Frame): The target frame to which the Element will be transformed. - Returns: + Returns + ------- Element: A new instance of the Element with the specified orientation applied. + """ new_instance = self.copy() new_instance.transform_from_frame_to_frame(source_frame, target_frame) @@ -1422,11 +1424,11 @@ def transformed_from_frame_to_frame(self, source_frame, target_frame): # ========================================================================== def __repr__(self): - """ - Return a string representation of the Element. + """Return a string representation of the Element. - Returns: + Returns str: The string representation of the Element. + """ return """{0} {1} {2}""".format( self.name, "_".join(map(str, self.id)), self.guid @@ -1438,13 +1440,10 @@ class _: """Internal geometry utilities""" class Ear: - """ - Represents an ear of a polygon. An ear is a triangle formed by three consecutive vertices of the polygon. - """ + """Represents an ear of a polygon. An ear is a triangle formed by three consecutive vertices of the polygon.""" def __init__(self, points, indexes, ind): - """ - Initialize an Ear instance. + """Initialize an Ear instance. Args: points (list): List of vertex coordinates. @@ -1463,14 +1462,15 @@ def __init__(self, points, indexes, ind): self.neighbour_coords = [points[self.prew], points[self.next]] def is_inside(self, point): - """ - Check if a given point is inside the triangle formed by the ear. + """Check if a given point is inside the triangle formed by the ear. Args: point (list): Coordinates of the point to check. - Returns: + Returns + ------- bool: True if the point is inside the triangle, False otherwise. + """ p1 = self.coords p2 = self.neighbour_coords[0] @@ -1488,31 +1488,34 @@ def is_inside(self, point): return False def is_ear_point(self, p): - """ - Check if a given point is one of the vertices of the ear triangle. + """Check if a given point is one of the vertices of the ear triangle. Args: p (list): Coordinates of the point to check. - Returns: + Returns + ------- bool: True if the point is a vertex of the ear triangle, False otherwise. + """ if p == self.coords or p in self.neighbour_coords: return True return False def validate(self, points, indexes, ears): - """ - Validate if the ear triangle is a valid ear by checking its convexity and that no points lie inside. + """Validate if the ear triangle is a valid ear by checking its convexity and that no points lie inside. Args: points (list): List of vertex coordinates. indexes (list): List of vertex indexes. ears (list): List of other ear triangles. - Returns: + Returns + ------- bool: True if the ear triangle is valid, False otherwise. + """ + not_ear_points = [ points[i] for i in indexes @@ -1527,11 +1530,12 @@ def validate(self, points, indexes, ears): return False def is_convex(self): - """ - Check if the ear triangle is convex. + """Check if the ear triangle is convex. - Returns: + Returns + ------- bool: True if the ear triangle is convex, False otherwise. + """ a = self.neighbour_coords[0] b = self.coords @@ -1543,25 +1547,24 @@ def is_convex(self): return True def get_triangle(self): - """ - Get the indices of the vertices forming the ear triangle. + """Get the indices of the vertices forming the ear triangle. - Returns: + Returns + ------- list: List of vertex indices forming the ear triangle. + """ return [self.prew, self.index, self.next] class Earcut: - """ - A class for triangulating a simple polygon using the ear-cutting algorithm. - """ + """A class for triangulating a simple polygon using the ear-cutting algorithm.""" def __init__(self, points): - """ - Initialize an Earcut instance with the input points. + """Initialize an Earcut instance with the input points. Args: points (list): List of vertex coordinates forming the polygon. + """ self.vertices = points self.ears = [] @@ -1570,9 +1573,7 @@ def __init__(self, points): self.length = len(points) def update_neighbours(self): - """ - Update the list of neighboring vertices. - """ + """Update the list of neighboring vertices.""" neighbours = [] self.neighbours = neighbours @@ -1582,6 +1583,7 @@ def add_ear(self, new_ear): Args: new_ear (Ear): The new ear triangle to be added. + """ self.ears.append(new_ear) self.neighbours.append(new_ear.prew) @@ -1590,6 +1592,7 @@ def add_ear(self, new_ear): def find_ears(self): """ Find valid ear triangles among the vertices and add them to the ears list. + """ i = 0 indexes = list(range(self.length)) @@ -1605,6 +1608,7 @@ def find_ears(self): def triangulate(self): """ Triangulate the polygon using the ear-cutting algorithm. + """ indexes = list(range(self.length)) self.find_ears() diff --git a/src/compas_model/model/element_node.py b/src/compas_model/model/element_node.py index 1907924f..a31f1b0c 100644 --- a/src/compas_model/model/element_node.py +++ b/src/compas_model/model/element_node.py @@ -10,13 +10,10 @@ class ElementNode(TreeNode): ---------- name : str, optional A name or identifier for the node. - element : Element, optional Element or any classes that inherits from Element class. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. - parent : Node, optional The parent node of this node. This input is required when the node is created separately (not by tree.add_element(...)) @@ -27,6 +24,7 @@ class ElementNode(TreeNode): ---------- element : Element, read-only Element object stored in the node or any classes that inherits from Element class. + """ def __init__(self, name=None, element=None, attributes=None, parent=None): @@ -38,8 +36,8 @@ def __init__(self, name=None, element=None, attributes=None, parent=None): # -------------------------------------------------------------------------- if isinstance(element, Element) is False: raise Exception("ElementNode should have an element input") - else: - self.attributes["my_object"] = element # node stores the Element object + + self._element = element # node stores the Element object # -------------------------------------------------------------------------- # make the node into a leaf, it has no children @@ -68,27 +66,30 @@ def data(self): "name": self.name, "attributes": self.attributes, "children": None, - "my_object": self.attributes["my_object"], + "element": self.element, } @classmethod def from_data(cls, data): - my_object = data["my_object"] - node = cls(name=data["name"], element=my_object, attributes=data["attributes"]) + element = data["element"] + node = cls(name=data["name"], element=element, attributes=data["attributes"]) node._children = None return node # ========================================================================== # Attributes # ========================================================================== + @property def element(self): - """Element object stored in the base Node class attributes dictionary "my_object" property + """Element object + Returns ------- Element + """ - return self.attributes["my_object"] + return self._element # ========================================================================== # Operators @@ -107,6 +108,7 @@ def __eq__(self, other): bool True if the guids are the same. False otherwise. + """ if self.element.guid == other.element.guid: return True @@ -119,7 +121,7 @@ def __eq__(self, other): def __repr__(self): return "<{}> {}, {}".format( - self.__class__.__name__, self.name, self.attributes["my_object"] + self.__class__.__name__, self.name, self.element ) def __str__(self): diff --git a/src/compas_model/model/element_tree.py b/src/compas_model/model/element_tree.py index 55dc9b2f..d0b4c894 100644 --- a/src/compas_model/model/element_tree.py +++ b/src/compas_model/model/element_tree.py @@ -1,5 +1,4 @@ from compas.datastructures import Tree -from compas_model.elements.element import Element from compas_model.model.element_node import ElementNode from compas_model.model.group_node import GroupNode @@ -11,10 +10,8 @@ class ElementTree(Tree): ---------- model : Model Model object to update the element dictionary and the graph. - name : str, optional A name or identifier for the tree. - attributes : dict, optional A dictionary of additional attributes to be associated with the tree. @@ -40,6 +37,7 @@ def __init__(self, model=None, name="root", attributes=None): # ========================================================================== # Serialization # ========================================================================== + @property def data(self): @@ -62,10 +60,10 @@ def from_data(cls, data): nodes = [] for node in data["nodes"]: - if isinstance(node["attributes"]["my_object"], Element): - nodes.append(ElementNode.from_data(node)) - else: + if node["children"]: nodes.append(GroupNode.from_data(node)) + else: + nodes.append(ElementNode.from_data(node)) for node in nodes: node._tree = model_tree @@ -74,28 +72,8 @@ def from_data(cls, data): return model_tree # ========================================================================== - # Properites + # Attributes # ========================================================================== - @property - def root(self): - """Root node of the tree. - - Returns - ------- - GroupNode - """ - return self._root - - @property - def model(self): - """Model object that the tree belongs to. - - Returns - ------- - Model - """ - - return self._model @property def number_of_elements(self): @@ -129,9 +107,6 @@ def print(self): def _print(node, depth=0): - # ------------------------------------------------------------------ - # print current node - # ------------------------------------------------------------------ parent_name = "None" if node.parent is None else node.parent.name print("-" * 100) message = ( @@ -151,16 +126,10 @@ def _print(node, depth=0): print(message) - # ------------------------------------------------------------------ - # recursion - # ------------------------------------------------------------------ if node.children is not None: for child in node.children: _print(child, depth + 1) - # ------------------------------------------------------------------ - # main call for the recursive printing - # ------------------------------------------------------------------ _print(self.root) # ========================================================================== @@ -172,15 +141,13 @@ def add_group(self, name=None, geometry=None, attributes=None, parent=None): Parameters ---------- - name : str, optional A name or identifier for the node. - geometry : Any, optional Geometry or any other property, when you want to give a group a shape besides name. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. + """ return self.root.add_group( name=name, geometry=geometry, attributes=attributes, parent=None @@ -193,10 +160,8 @@ def add_element(self, name=None, element=None, attributes=None, parent=None): ---------- name : str, optional A name or identifier for the node. - element : Element, optional Element or any classes that inherits from Element class. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. @@ -204,6 +169,7 @@ def add_element(self, name=None, element=None, attributes=None, parent=None): ------- ElementNode ElementNode object or any class that inherits from ElementNode class. + """ return self.root.add_element( name=name, element=element, attributes=attributes, parent=None diff --git a/src/compas_model/model/group_node.py b/src/compas_model/model/group_node.py index 841fc7c4..1c66e348 100644 --- a/src/compas_model/model/group_node.py +++ b/src/compas_model/model/group_node.py @@ -4,6 +4,7 @@ class GroupNode(TreeNode): + """A group node that is represented by a name and a geometry and a list of children @@ -11,10 +12,8 @@ class GroupNode(TreeNode): ---------- name : str, optional A name or identifier for the node. - geometry : Any, optional Geometry or any other property, when you want to give a group a shape besides name. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. @@ -28,6 +27,7 @@ class GroupNode(TreeNode): ---------- geometry : Geometry, read-only The geometry of the node, if it is assigned. + """ def __init__(self, name=None, geometry=None, attributes=None, parent=None): @@ -37,7 +37,7 @@ def __init__(self, name=None, geometry=None, attributes=None, parent=None): # -------------------------------------------------------------------------- # geometry of the group node # -------------------------------------------------------------------------- - self.attributes["my_object"] = geometry # node stores the Element object + self._geometry = geometry # node stores the Element object # -------------------------------------------------------------------------- # when a node is created separately, a user must define the parent node: @@ -61,16 +61,29 @@ def data(self): "name": self.name, "attributes": self.attributes, "children": [child.data for child in self.children], - "my_object": self.attributes["my_object"], + "geometry": self.geometry, } @classmethod def from_data(cls, data): - my_object = data["my_object"] - node = cls(name=data["name"], geometry=my_object, attributes=data["attributes"]) - if data["children"] is not None: - for child in data["children"]: + geometry = data["geometry"] + + # empty root node - it has not children at this step + node = cls(name=data["name"], geometry=geometry, attributes=data["attributes"]) + + # add children and sub-children + for child in data["children"]: + if child["children"]: + # recursively add children node.add(cls.from_data(child)) + else: + # otherwise add a leaf + node.add_element( + name=child["name"], + element=child["element"], + attributes=child["attributes"], + ) + return node # ========================================================================== @@ -79,12 +92,14 @@ def from_data(cls, data): @property def geometry(self): - """Geometry object stored in the base Node class attributes dictionary "my_object" property + """Geometry object + Returns ------- Any + """ - return self.attributes["my_object"] + return self._geometry # ========================================================================== # Operators @@ -102,6 +117,7 @@ def __eq__(self, other): ------- bool True if the two nodes are the same, False otherwise. + """ if self.guid == other.guid: return True @@ -114,7 +130,7 @@ def __eq__(self, other): def __repr__(self): return "<{}> {}, {}".format( - self.__class__.__name__, self.name, self.attributes["my_object"] + self.__class__.__name__, self.name, self.geometry ) def __str__(self): @@ -124,46 +140,38 @@ def add_element( self, name=None, element=None, attributes=None, copy_element=False, parent=None ): - """add element to the tree + """Add element to the group + + Triple Behavior: - triple behavior: - 1. if Element_Node exists, add an element to the base dictionary of Model class - 2. if Element_Node exists, add an element to the graph - 3. add a node tree + 1. If Element_Node exists, add an element to the base dictionary of the Model class. + 2. If Element_Node exists, add an element to the graph. + 3. Add a node tree. Parameters ---------- - name : str or Element - two possibilities: 1. my_node.add_element(name, element) 2. my_node.add_element(element) - + name : str + a name of the ElementNode. element : Element Element object or any class that inherits from Element class. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. - copy_element : bool, optional If True, the element is copied before adding to the tree. - parent : Node, optional The parent node of this node. - """ + """ # ----------------------------------------------------------------------- - # usert interface + # user interface # 1 user did not provide anything -> return None # 2 user provided element input -> my_node.add_element(name, element) - # 3 user provided name input as input -> my_node.add_element(element) - # 4 otherwise there is an exception + # 3 otherwise there is an exception # ----------------------------------------------------------------------- - if element is None and name is None: - return None - elif isinstance(element, Element): + + if isinstance(element, Element): element_copy = element.copy() if copy_element else element name = name if name else str(element_copy.guid) - elif isinstance(name, Element) and element is None: - element_copy = name.copy() if copy_element else name - name = element_copy.guid else: raise Exception("At least provide element input") @@ -179,10 +187,13 @@ def add_element( parent=element_copy.parent, ) + # ----------------------------------------------------------------------- + # add the node to the children list + # ----------------------------------------------------------------------- self._children.append(node) # ----------------------------------------------------------------------- - # add the node to the tree regardless what node it is + # add elements to the base dictionary of Model class and graph # ----------------------------------------------------------------------- if self._tree is not None: if self._tree._model is not None: @@ -193,24 +204,20 @@ def add_element( return node def add_group(self, name=None, geometry=None, attributes=None, parent=None): - """add group to the tree + """add group to the group Parameters ---------- name : str, optional A name or identifier for the node. - geometry : Any, optional Geometry or any other property, when you want to give a group a shape besides name. - attributes : dict, optional A dictionary of additional attributes to be associated with the node. - parent : Node, optional The parent node of this node. """ - # ----------------------------------------------------------------------- # if parent is not provided, use the node that called this method # ----------------------------------------------------------------------- diff --git a/src/compas_model/model/model.py b/src/compas_model/model/model.py index 8f28ab37..7f49848b 100644 --- a/src/compas_model/model/model.py +++ b/src/compas_model/model/model.py @@ -16,10 +16,8 @@ class Model(Data): ---------- name : str, optional A name or identifier for the model. - elements : list, optional A list of elements to be added to the model. - copy_elements : bool, optional If True, the elements are copied before adding to the model. @@ -90,25 +88,25 @@ def interactions(self): # ========================================================================== @property def number_of_elements(self): - """ - Get the number of elements in the model. + """Get the number of elements in the model. Returns ------- int The total number of elements in the model. + """ return len(list(self.elements)) @property def number_of_nodes(self): - """ - Count the total number of children in the tree hierarchy. + """Count the total number of children in the tree hierarchy. Returns ------- int The total number of child nodes in the tree hierarchy. + """ def _count(node): @@ -124,13 +122,13 @@ def _count(node): @property def number_of_edges(self): - """ - Get the number of edges in the model's interactions. + """Get the number of edges in the model's interactions. Returns ------- int The total number of edges in the interactions graph of the model. + """ return self._interactions.number_of_edges() @@ -138,10 +136,10 @@ def number_of_edges(self): # Printing # ========================================================================== def print_elements(self): - """ - Print all elements in the model. + """Print all elements in the model. This method prints all elements in the model to the console. + """ print( "================================== {} ===================================".format( @@ -158,10 +156,9 @@ def print_elements(self): ) def print_interactions(self): - """ - Print all interactions between elements. - + """Print all interactions between elements. This method prints all interactions between elements in the model to the console. + """ print( "================================== {} ===================================".format( @@ -196,14 +193,12 @@ def __str__(self): return self.__repr__() def print(self): - """ - Print the spatial hierarchy of the tree for debugging and visualization. + """Print the spatial hierarchy of the tree for debugging and visualization. This method prints information about the tree's spatial hierarchy, including nodes, elements, parent-child relationships, and other relevant details. """ - # ------------------------------------------------------------------ # print hierarchy # ------------------------------------------------------------------ @@ -253,6 +248,7 @@ def _print(node, depth=0): # ========================================================================== # Behavior - Hierarchy # ========================================================================== + def add_elements(self, elements=[], copy_elements=False): """Adds elements to the model. @@ -260,10 +256,8 @@ def add_elements(self, elements=[], copy_elements=False): Parameters ---------- - elements : list, optional A list of elements to be added to the model. - copy_elements : bool, optional If True, the elements are copied before adding to the model. @@ -271,11 +265,12 @@ def add_elements(self, elements=[], copy_elements=False): ------- list : guid A list of identifiers for the elements. + """ guids = [] for element in elements: guids.append( - self._tree_utils.add_element( + self._hierarchy.root.add_element( name=None, element=element, copy_element=copy_elements ) ) @@ -290,21 +285,18 @@ def add_element(self, name=None, element=None, attributes=None, copy_element=Fal ---------- name : str, optional A name or identifier for the element. - element : Element, optional Element or any classes that inherits from Element class. - attributes : dict, optional A dictionary of additional attributes to be associated with the element. - copy_element : bool, optional If True, the element is copied before adding to the model. Returns ------- ElementNode - """ + """ return self.hierarchy.root.add_element( name=name, element=element, @@ -320,16 +312,15 @@ def add_group(self, name=None, geometry=None, attributes=None): ---------- name : str, optional A name or identifier for the group. - geometry : Any, optional Geometry or any other property, when you want to give a group a shape besides name. - attributes : dict, optional A dictionary of additional attributes to be associated with the group. Returns ------- GroupNode + """ return self.hierarchy.root.add_group( name=name, @@ -342,8 +333,7 @@ def add_group(self, name=None, geometry=None, attributes=None): # Behavior - Interactions # ========================================================================== def add_interaction(self, element0, element1, geometry=None, weight=1): - """ - Adds an interaction between two elements in the model. + """Adds an interaction between two elements in the model. This method allows you to establish an interaction between two elements within the model. @@ -351,7 +341,6 @@ def add_interaction(self, element0, element1, geometry=None, weight=1): ---------- element0 : Element The first element involved in the interaction. - element1 : Element The second element involved in the interaction. @@ -359,6 +348,7 @@ def add_interaction(self, element0, element1, geometry=None, weight=1): ------- tuple[hashable, hashable] The identifier of the edge. + """ # ------------------------------------------------------------------ # check if user inputs ElementNode or Element @@ -385,8 +375,7 @@ def add_interaction(self, element0, element1, geometry=None, weight=1): # Copy # ========================================================================== def copy(self): - """copy the model""" - + """copy the model and duplicate all elements, tree nodes and graph nodes""" # -------------------------------------------------------------------------- # create the empty model # -------------------------------------------------------------------------- @@ -421,9 +410,7 @@ def copy_hierarchy(current_node, copy_node): elif isinstance(child, GroupNode): # copy the group name = child.name - geometry = ( - None if child.geometry is None else child._my_object.copy() - ) + geometry = None if child.geometry is None else child.geometry.copy() # add the group to the parent last_group_node = copy_node.add_group(name=name, geometry=geometry) # -------------------------------------------------------------------------- diff --git a/tests/test_model.py b/tests/test_model.py index 9428bc81..d40cd4ef 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -13,11 +13,16 @@ def create_model(): # add group nodes - a typical tree node with a name and geometry car = model.add_group(name="car", geometry=None) # type: ignore + interior = model.add_group(name="interior", geometry=None) # type: ignore + seats = interior.add_group(name="seats", geometry=None) # type: ignore + front_seat = seats.add_group(name="front_seat", geometry=None) # type: ignore wheel = car.add_group(name="wheel", geometry=Point(0, 0, 0)) # type: ignore # add element nodes - a "special" tree node with a name and element wheel.add_element(name="spoke1", element=Element.from_frame(1, 10, 1)) # type: ignore wheel.add_element(name="spoke2", element=Element.from_frame(5, 10, 1)) # type: ignore + front_seat.add_element(name="seat1", element=Element.from_frame(1, 10, 1)) # type: ignore + front_seat.add_element(name="seat2", element=Element.from_frame(5, 10, 1)) # type: ignore # print the model to preview the tree structure # model.print() @@ -88,10 +93,10 @@ def copy_model(): ) # the root of hierarchy automatically initializes the root node as truss1 = model.add_group("truss1") truss2 = model.add_group("truss2") - truss1.add_element(e0) - truss1.add_element(e1) - truss2.add_element(e2) - truss2.add_element(e3) + truss1.add_element(e0.name, e0) + truss1.add_element(e1.name, e1) + truss2.add_element(e2.name, e2) + truss2.add_element(e3.name, e3) model.print() model.add_interaction(e0, e1) @@ -219,7 +224,7 @@ def serialize_model(): if __name__ == "__main__": - # model = create_model().print() + model = create_model().print() # model = create_model_with_interactions() # model = create_model_without_hierarchy() @@ -228,4 +233,4 @@ def serialize_model(): # serialize_element() # serialize_model_node() # serialize_model_tree() - serialize_model() + # serialize_model()