diff --git a/CHANGELOG.md b/CHANGELOG.md index e60d02c3fa4f..fe3fecb3b43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Changed `compas_rhino.drawing.draw_breps` to assume provided polygon is closed and automatically add missing corner to polycurve constructor. * Changed conversion of edges and faces to uniques keys for the data dicts to use the string representation of a sorted tuple of identifiers. * Added `dtype` to JSON decoding error message. +* Moved `compas.datastructures.mesh.core.halfedge.HalfEdge` to `compas.datastructures.halfedge.halfedge.HalfEdge` +* Moved `compas.datastructures.network.core.graph.Graph` to `compas.datastructures.graph.graph.Graph`. ### Removed +* Removed `compas.datastructures.mesh.core.mesh.BaseMesh`. + +* Removed `compas.datastructures.BaseNetwork`. + ## [1.7.1] 2021-06-14 ### Added diff --git a/src/compas/datastructures/__init__.py b/src/compas/datastructures/__init__.py index f1dbd5b26809..2240f36b7b4e 100644 --- a/src/compas/datastructures/__init__.py +++ b/src/compas/datastructures/__init__.py @@ -5,21 +5,27 @@ .. currentmodule:: compas.datastructures -Network -======= - Classes -------- +======= .. autosummary:: :toctree: generated/ :nosignatures: + Datastructure + Graph + HalfEdge + HalfFace + Mesh Network + VolMesh Functions ---------- +========= + +Network +------- .. autosummary:: :toctree: generated/ @@ -47,10 +53,6 @@ network_transform network_transformed - -CPython-only ------------- - .. autosummary:: :toctree: generated/ :nosignatures: @@ -62,17 +64,7 @@ Mesh -==== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - Mesh - - -Functions ---------- +---- .. autosummary:: :toctree: generated/ @@ -142,10 +134,6 @@ trimesh_subdivide_loop trimesh_swap_edge - -CPython-only ------------- - .. autosummary:: :toctree: generated/ :nosignatures: @@ -171,21 +159,8 @@ VolMesh -======= - -Classes ------- -.. autosummary:: - :toctree: generated/ - :nosignatures: - - VolMesh - - -Functions ---------- - .. autosummary:: :toctree: generated/ :nosignatures: @@ -200,9 +175,11 @@ import compas from .datastructure import Datastructure + +from .graph import ( + Graph +) from .network import ( - BaseNetwork, # NOTE: this class being in the stable API is something we should deprecate before 2.x release - Graph, Network, network_complement, network_count_crossings, @@ -226,9 +203,10 @@ network_transform, network_transformed, ) +from .halfedge import ( + HalfEdge +) from .mesh import ( - BaseMesh, # NOTE: this class being in the stable API is something we should deprecate before 2.x release - HalfEdge, Mesh, mesh_add_vertex_to_face_edge, mesh_bounding_box_xy, @@ -294,9 +272,10 @@ trimesh_subdivide_loop, trimesh_swap_edge, ) +from .halfface import ( + HalfFace +) from .volmesh import ( - BaseVolMesh, # NOTE: this class being in the stable API is something we should deprecate before 2.x release - HalfFace, VolMesh, volmesh_bounding_box, volmesh_transform, @@ -331,11 +310,16 @@ trimesh_vertexarea_matrix, ) +BaseNetwork = Network +BaseMesh = Mesh +BaseVolMesh = VolMesh + __all__ = [ 'Datastructure', + # Graphs + 'Graph', # Networks 'BaseNetwork', - 'Graph', 'Network', 'network_complement', 'network_count_crossings', @@ -358,9 +342,10 @@ 'network_split_edge', 'network_transform', 'network_transformed', + # HalfEdge + 'HalfEdge', # Meshes 'BaseMesh', - 'HalfEdge', 'Mesh', 'mesh_add_vertex_to_face_edge', 'mesh_bounding_box_xy', @@ -425,9 +410,10 @@ 'trimesh_split_edge', 'trimesh_subdivide_loop', 'trimesh_swap_edge', + # HalfFace + 'HalfFace', # Volumetric Meshes 'BaseVolMesh', - 'HalfFace', 'VolMesh', 'volmesh_bounding_box', 'volmesh_transform', diff --git a/src/compas/datastructures/datastructure.py b/src/compas/datastructures/datastructure.py index b39fb0a47b4a..2b13a4e3e804 100644 --- a/src/compas/datastructures/datastructure.py +++ b/src/compas/datastructures/datastructure.py @@ -8,6 +8,7 @@ class Datastructure(Data): + """Base class for all data structures.""" def __init__(self): super(Datastructure, self).__init__() diff --git a/src/compas/datastructures/graph/__init__.py b/src/compas/datastructures/graph/__init__.py new file mode 100644 index 000000000000..b040d9104205 --- /dev/null +++ b/src/compas/datastructures/graph/__init__.py @@ -0,0 +1 @@ +from .graph import Graph # noqa: F401 diff --git a/src/compas/datastructures/network/core/graph.py b/src/compas/datastructures/graph/graph.py similarity index 97% rename from src/compas/datastructures/network/core/graph.py rename to src/compas/datastructures/graph/graph.py index bb32afe0562b..0d7b0eb10b97 100644 --- a/src/compas/datastructures/network/core/graph.py +++ b/src/compas/datastructures/graph/graph.py @@ -17,6 +17,16 @@ class Graph(Datastructure): """Base graph data structure for describing the topological relationships between nodes connected by edges. + Parameters + ---------- + name: str, optional + The name of the graph. + Defaults to "Graph". + default_node_attributes: dict, optional + Default values for node attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + Attributes ---------- node : dict @@ -65,18 +75,22 @@ def DATASCHEMA(self): def JSONSCHEMANAME(self): return 'graph' - def __init__(self): + def __init__(self, name=None, default_node_attributes=None, default_edge_attributes=None): super(Graph, self).__init__() self._max_node = -1 - self.attributes = {'name': 'Graph'} + self.attributes = {'name': name or 'Graph'} self.node = {} self.edge = {} self.adjacency = {} self.default_node_attributes = {} self.default_edge_attributes = {} + if default_node_attributes: + self.default_node_attributes.update(default_node_attributes) + if default_edge_attributes: + self.default_edge_attributes.update(default_edge_attributes) def __str__(self): - tpl = "" + tpl = "" return tpl.format(self.number_of_nodes(), self.number_of_edges()) # -------------------------------------------------------------------------- @@ -352,7 +366,7 @@ def index_uv(self): # builders # -------------------------------------------------------------------------- - def add_node(self, key, attr_dict=None, **kwattr): + def add_node(self, key=None, attr_dict=None, **kwattr): """Add a node and specify its attributes (optional). Parameters @@ -367,7 +381,7 @@ def add_node(self, key, attr_dict=None, **kwattr): Returns ------- - str + hashable The key of the node. Notes @@ -382,6 +396,14 @@ def add_node(self, key, attr_dict=None, **kwattr): -------- >>> """ + if key is None: + key = self._max_node = self._max_node + 1 + try: + if key > self._max_node: + self._max_node = key + except (ValueError, TypeError): + pass + if key not in self.node: self.node[key] = {} self.edge[key] = {} diff --git a/src/compas/datastructures/halfedge/__init__.py b/src/compas/datastructures/halfedge/__init__.py new file mode 100644 index 000000000000..be40e19a9109 --- /dev/null +++ b/src/compas/datastructures/halfedge/__init__.py @@ -0,0 +1 @@ +from .halfedge import HalfEdge # noqa: F401 diff --git a/src/compas/datastructures/mesh/core/halfedge.py b/src/compas/datastructures/halfedge/halfedge.py similarity index 98% rename from src/compas/datastructures/mesh/core/halfedge.py rename to src/compas/datastructures/halfedge/halfedge.py index be3cfc46c5b4..0a996b30de44 100644 --- a/src/compas/datastructures/mesh/core/halfedge.py +++ b/src/compas/datastructures/halfedge/halfedge.py @@ -15,7 +15,19 @@ class HalfEdge(Datastructure): - """Base half-edge data structure for representing meshes. + """Base half-edge data structure for representing the topology of open oor closed surface meshes. + + Parameters + ---------- + name: str, optional + The name of the graph. + Defaults to "Graph". + default_vertex_attributes: dict, optional + Default values for vertex attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + default_face_attributes: dict, optional + Default values for face attributes. Attributes ---------- @@ -51,7 +63,6 @@ def DATASCHEMA(self): "vertex": schema.And( dict, is_sequence_of_uint, - # lambda x: all(('x' in attr and 'y' in attr and 'z' in attr) for attr in x.values()) ), "face": schema.And( dict, @@ -79,7 +90,7 @@ def DATASCHEMA(self): def JSONSCHEMANAME(self): return 'halfedge' - def __init__(self): + def __init__(self, name=None, default_vertex_attributes=None, default_edge_attributes=None, default_face_attributes=None): super(HalfEdge, self).__init__() self._max_vertex = -1 self._max_face = -1 @@ -88,13 +99,19 @@ def __init__(self): self.face = {} self.facedata = {} self.edgedata = {} - self.attributes = {'name': 'Mesh'} - self.default_vertex_attributes = {'x': 0.0, 'y': 0.0, 'z': 0.0} + self.attributes = {'name': name or 'HalfEdge'} + self.default_vertex_attributes = {} self.default_edge_attributes = {} self.default_face_attributes = {} + if default_vertex_attributes: + self.default_vertex_attributes.update(default_vertex_attributes) + if default_edge_attributes: + self.default_edge_attributes.update(default_edge_attributes) + if default_face_attributes: + self.default_face_attributes.update(default_face_attributes) def __str__(self): - tpl = "" + tpl = "" return tpl.format(self.number_of_vertices(), self.number_of_faces(), self.number_of_edges()) # -------------------------------------------------------------------------- diff --git a/src/compas/datastructures/halfface/__init__.py b/src/compas/datastructures/halfface/__init__.py new file mode 100644 index 000000000000..8fea78fb4a79 --- /dev/null +++ b/src/compas/datastructures/halfface/__init__.py @@ -0,0 +1 @@ +from .halfface import HalfFace # noqa: F401 diff --git a/src/compas/datastructures/volmesh/core/halfface.py b/src/compas/datastructures/halfface/halfface.py similarity index 98% rename from src/compas/datastructures/volmesh/core/halfface.py rename to src/compas/datastructures/halfface/halfface.py index 79a17b6f811a..d9e8a76ffeff 100644 --- a/src/compas/datastructures/volmesh/core/halfface.py +++ b/src/compas/datastructures/halfface/halfface.py @@ -19,6 +19,20 @@ class HalfFace(Datastructure): """Base half-face data structure fore representing volumetric meshes. + Parameters + ---------- + name: str, optional + The name of the graph. + Defaults to "Graph". + default_vertex_attributes: dict, optional + Default values for vertex attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + default_face_attributes: dict, optional + Default values for face attributes. + default_cell_attributes: dict, optional + Default values for cell attributes. + Attributes ---------- attributes : dict @@ -60,7 +74,12 @@ def DATASCHEMA(self): def JSONSCHEMANAME(self): return 'halfface' - def __init__(self): + def __init__(self, + name=None, + default_vertex_attributes=None, + default_edge_attributes=None, + default_face_attributes=None, + default_cell_attributes=None): super(HalfFace, self).__init__() self._max_vertex = -1 self._max_face = -1 @@ -72,14 +91,22 @@ def __init__(self): self._edge_data = {} self._face_data = {} self._cell_data = {} - self.attributes = {'name': 'VolMesh'} + self.attributes = {'name': name or 'HalfFace'} self.default_vertex_attributes = {'x': 0.0, 'y': 0.0, 'z': 0.0} self.default_edge_attributes = {} self.default_face_attributes = {} self.default_cell_attributes = {} + if default_vertex_attributes: + self.default_vertex_attributes.update(default_vertex_attributes) + if default_edge_attributes: + self.default_edge_attributes.update(default_edge_attributes) + if default_face_attributes: + self.default_face_attributes.update(default_face_attributes) + if default_cell_attributes: + self.default_cell_attributes.update(default_cell_attributes) def __str__(self): - tpl = "" + tpl = "" return tpl.format(self.number_of_vertices(), self.number_of_faces(), self.number_of_cells(), self.number_of_edges()) # -------------------------------------------------------------------------- diff --git a/src/compas/datastructures/mesh/__init__.py b/src/compas/datastructures/mesh/__init__.py index f6bf7748c083..16d74c94d922 100644 --- a/src/compas/datastructures/mesh/__init__.py +++ b/src/compas/datastructures/mesh/__init__.py @@ -4,7 +4,11 @@ from compas import IPY -from .core import * # noqa: F401 F403 +if not IPY: + from .matrices import * # noqa: F401 F403 + +from .operations import * # noqa: F401 F403 +from .clean import * # noqa: F401 F403 from .bbox import * # noqa: F401 F403 from .combinatorics import * # noqa: F401 F403 @@ -36,7 +40,7 @@ from .transformations_numpy import * # noqa: F401 F403 from .trimesh_samplepoints_numpy import * # noqa: F401 F403 -from ._mesh import * # noqa: F401 F403 +from .mesh import Mesh # noqa: F401 __all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/compas/datastructures/mesh/_mesh.py b/src/compas/datastructures/mesh/_mesh.py deleted file mode 100644 index 026b3988a072..000000000000 --- a/src/compas/datastructures/mesh/_mesh.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.datastructures.mesh.core import BaseMesh -from compas.datastructures.mesh.core import mesh_collapse_edge -from compas.datastructures.mesh.core import mesh_split_edge -from compas.datastructures.mesh.core import mesh_split_face -from compas.datastructures.mesh.core import mesh_merge_faces - -from compas.datastructures.mesh.bbox import mesh_bounding_box -from compas.datastructures.mesh.bbox import mesh_bounding_box_xy -from compas.datastructures.mesh.combinatorics import mesh_is_connected -from compas.datastructures.mesh.combinatorics import mesh_connected_components -from compas.datastructures.mesh.duality import mesh_dual -from compas.datastructures.mesh.orientation import mesh_face_adjacency -from compas.datastructures.mesh.orientation import mesh_flip_cycles -from compas.datastructures.mesh.orientation import mesh_unify_cycles -from compas.datastructures.mesh.slice import mesh_slice_plane -from compas.datastructures.mesh.smoothing import mesh_smooth_centroid -from compas.datastructures.mesh.smoothing import mesh_smooth_area -from compas.datastructures.mesh.subdivision import mesh_subdivide -from compas.datastructures.mesh.transformations import mesh_transform -from compas.datastructures.mesh.transformations import mesh_transformed -from compas.datastructures.mesh.triangulation import mesh_quads_to_triangles - - -__all__ = ['Mesh'] - - -class Mesh(BaseMesh): - """Implementation of the base mesh data structure that adds some of the mesh algorithms as methods. - - Examples - -------- - >>> mesh = Mesh.from_polyhedron(6) - - """ - - bounding_box = mesh_bounding_box - bounding_box_xy = mesh_bounding_box_xy - collapse_edge = mesh_collapse_edge - connected_components = mesh_connected_components - dual = mesh_dual - face_adjacency = mesh_face_adjacency - flip_cycles = mesh_flip_cycles - is_connected = mesh_is_connected - merge_faces = mesh_merge_faces - slice_plane = mesh_slice_plane - smooth_centroid = mesh_smooth_centroid - smooth_area = mesh_smooth_area - split_edge = mesh_split_edge - split_face = mesh_split_face - subdivide = mesh_subdivide - transform = mesh_transform - transformed = mesh_transformed - unify_cycles = mesh_unify_cycles - quads_to_triangles = mesh_quads_to_triangles diff --git a/src/compas/datastructures/mesh/core/clean.py b/src/compas/datastructures/mesh/clean.py similarity index 100% rename from src/compas/datastructures/mesh/core/clean.py rename to src/compas/datastructures/mesh/clean.py diff --git a/src/compas/datastructures/mesh/core/__init__.py b/src/compas/datastructures/mesh/core/__init__.py deleted file mode 100644 index d880072a1c14..000000000000 --- a/src/compas/datastructures/mesh/core/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas import IPY - -from .halfedge import HalfEdge # noqa: F401 -from .mesh import BaseMesh # noqa: F401 -from .operations import * # noqa: F401 F403 -from .clean import * # noqa: F401 F403 - -if not IPY: - from .matrices import * # noqa: F401 F403 - -__all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/compas/datastructures/mesh/geodesics_numpy.py b/src/compas/datastructures/mesh/geodesics_numpy.py index 768d366eb18c..4c85b78098e4 100644 --- a/src/compas/datastructures/mesh/geodesics_numpy.py +++ b/src/compas/datastructures/mesh/geodesics_numpy.py @@ -17,7 +17,7 @@ from compas.numerical import normrow from compas.numerical import normalizerow -from compas.datastructures.mesh.core import trimesh_cotangent_laplacian_matrix +from .matrices import trimesh_cotangent_laplacian_matrix __all__ = ['mesh_geodesic_distances_numpy'] diff --git a/src/compas/datastructures/mesh/core/matrices.py b/src/compas/datastructures/mesh/matrices.py similarity index 100% rename from src/compas/datastructures/mesh/core/matrices.py rename to src/compas/datastructures/mesh/matrices.py diff --git a/src/compas/datastructures/mesh/core/mesh.py b/src/compas/datastructures/mesh/mesh.py similarity index 92% rename from src/compas/datastructures/mesh/core/mesh.py rename to src/compas/datastructures/mesh/mesh.py index 6db46617940e..3034453ef582 100644 --- a/src/compas/datastructures/mesh/core/mesh.py +++ b/src/compas/datastructures/mesh/mesh.py @@ -6,7 +6,7 @@ import sys from math import pi -from compas.datastructures.mesh.core.halfedge import HalfEdge +import compas from compas.files import OBJ from compas.files import OFF @@ -37,13 +37,45 @@ from compas.utilities import pairwise from compas.utilities import window - -__all__ = ['BaseMesh'] - - -class BaseMesh(HalfEdge): +from compas.datastructures import HalfEdge + +from .operations import mesh_collapse_edge +from .operations import mesh_split_edge +from .operations import mesh_split_face +from .operations import mesh_merge_faces + +from .bbox import mesh_bounding_box +from .bbox import mesh_bounding_box_xy +from .combinatorics import mesh_is_connected +from .combinatorics import mesh_connected_components +from .duality import mesh_dual +from .orientation import mesh_face_adjacency +from .orientation import mesh_flip_cycles +from .orientation import mesh_unify_cycles +from .slice import mesh_slice_plane +from .smoothing import mesh_smooth_centroid +from .smoothing import mesh_smooth_area +from .subdivision import mesh_subdivide +from .transformations import mesh_transform +from .transformations import mesh_transformed +from .triangulation import mesh_quads_to_triangles + + +class Mesh(HalfEdge): """Geometric implementation of a half edge data structure for polygon meshses. + Parameters + ---------- + name: str, optional + The name of the graph. + Defaults to "Graph". + default_vertex_attributes: dict, optional + Default values for vertex attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + default_face_attributes: dict, optional + Default values for face attributes. + Attributes ---------- attributes : dict @@ -73,10 +105,52 @@ class BaseMesh(HalfEdge): """ - def __init__(self): - super(BaseMesh, self).__init__() - self.attributes.update({'name': 'Mesh'}) - self.default_vertex_attributes.update({'x': 0.0, 'y': 0.0, 'z': 0.0}) + bounding_box = mesh_bounding_box + bounding_box_xy = mesh_bounding_box_xy + collapse_edge = mesh_collapse_edge + connected_components = mesh_connected_components + dual = mesh_dual + face_adjacency = mesh_face_adjacency + flip_cycles = mesh_flip_cycles + is_connected = mesh_is_connected + merge_faces = mesh_merge_faces + slice_plane = mesh_slice_plane + smooth_centroid = mesh_smooth_centroid + smooth_area = mesh_smooth_area + split_edge = mesh_split_edge + split_face = mesh_split_face + subdivide = mesh_subdivide + transform = mesh_transform + transformed = mesh_transformed + unify_cycles = mesh_unify_cycles + quads_to_triangles = mesh_quads_to_triangles + + if not compas.IPY: + from .bbox_numpy import mesh_oriented_bounding_box_numpy + from .bbox_numpy import mesh_oriented_bounding_box_xy_numpy + + obb_numpy = mesh_oriented_bounding_box_numpy + obb_xy_numpy = mesh_oriented_bounding_box_xy_numpy + + def __init__(self, name=None, default_vertex_attributes=None, default_edge_attributes=None, default_face_attributes=None): + name = name or 'Mesh' + _default_vertex_attributes = {'x': 0.0, 'y': 0.0, 'z': 0.0} + _default_edge_attributes = {} + _default_face_attributes = {} + if default_vertex_attributes: + _default_vertex_attributes.update(default_vertex_attributes) + if default_edge_attributes: + _default_edge_attributes.update(default_edge_attributes) + if default_face_attributes: + _default_face_attributes.update(default_face_attributes) + super(Mesh, self).__init__(name=name, + default_vertex_attributes=_default_vertex_attributes, + default_edge_attributes=_default_edge_attributes, + default_face_attributes=_default_face_attributes) + + def __str__(self): + tpl = "" + return tpl.format(self.number_of_vertices(), self.number_of_faces(), self.number_of_edges()) # -------------------------------------------------------------------------- # customisation diff --git a/src/compas/datastructures/mesh/offset.py b/src/compas/datastructures/mesh/offset.py index 3d942db69367..ed938b8df10a 100644 --- a/src/compas/datastructures/mesh/offset.py +++ b/src/compas/datastructures/mesh/offset.py @@ -5,8 +5,8 @@ from compas.geometry import add_vectors from compas.geometry import scale_vector -from compas.datastructures.mesh.orientation import mesh_flip_cycles -from compas.datastructures.mesh.join import meshes_join +from .orientation import mesh_flip_cycles +from .join import meshes_join __all__ = [ 'mesh_offset', diff --git a/src/compas/datastructures/mesh/core/operations/__init__.py b/src/compas/datastructures/mesh/operations/__init__.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/__init__.py rename to src/compas/datastructures/mesh/operations/__init__.py diff --git a/src/compas/datastructures/mesh/core/operations/collapse.py b/src/compas/datastructures/mesh/operations/collapse.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/collapse.py rename to src/compas/datastructures/mesh/operations/collapse.py diff --git a/src/compas/datastructures/mesh/core/operations/insert.py b/src/compas/datastructures/mesh/operations/insert.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/insert.py rename to src/compas/datastructures/mesh/operations/insert.py diff --git a/src/compas/datastructures/mesh/core/operations/merge.py b/src/compas/datastructures/mesh/operations/merge.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/merge.py rename to src/compas/datastructures/mesh/operations/merge.py diff --git a/src/compas/datastructures/mesh/core/operations/split.py b/src/compas/datastructures/mesh/operations/split.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/split.py rename to src/compas/datastructures/mesh/operations/split.py diff --git a/src/compas/datastructures/mesh/core/operations/substitute.py b/src/compas/datastructures/mesh/operations/substitute.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/substitute.py rename to src/compas/datastructures/mesh/operations/substitute.py diff --git a/src/compas/datastructures/mesh/core/operations/swap.py b/src/compas/datastructures/mesh/operations/swap.py similarity index 100% rename from src/compas/datastructures/mesh/core/operations/swap.py rename to src/compas/datastructures/mesh/operations/swap.py diff --git a/src/compas/datastructures/mesh/core/operations/weld.py b/src/compas/datastructures/mesh/operations/weld.py similarity index 96% rename from src/compas/datastructures/mesh/core/operations/weld.py rename to src/compas/datastructures/mesh/operations/weld.py index 0e9a77658a5c..78eb54b650a3 100644 --- a/src/compas/datastructures/mesh/core/operations/weld.py +++ b/src/compas/datastructures/mesh/operations/weld.py @@ -5,10 +5,11 @@ from compas.topology import adjacency_from_edges from compas.topology import connected_components -from compas.datastructures.mesh.core.operations.substitute import mesh_substitute_vertex_in_faces - from compas.utilities import pairwise +from .substitute import mesh_substitute_vertex_in_faces + + __all__ = [ 'mesh_unweld_vertices', 'mesh_unweld_edges' diff --git a/src/compas/datastructures/mesh/remesh.py b/src/compas/datastructures/mesh/remesh.py index a4953657ca60..166c410d9f2a 100644 --- a/src/compas/datastructures/mesh/remesh.py +++ b/src/compas/datastructures/mesh/remesh.py @@ -2,10 +2,10 @@ from __future__ import absolute_import from __future__ import division -from compas.datastructures.mesh.smoothing import mesh_smooth_area -from compas.datastructures.mesh.core import trimesh_collapse_edge -from compas.datastructures.mesh.core import trimesh_swap_edge -from compas.datastructures.mesh.core import trimesh_split_edge +from .smoothing import mesh_smooth_area +from .operations import trimesh_collapse_edge +from .operations import trimesh_swap_edge +from .operations import trimesh_split_edge __all__ = [ diff --git a/src/compas/datastructures/mesh/smoothing_numpy.py b/src/compas/datastructures/mesh/smoothing_numpy.py index 7ee6337f9fb0..e1d990b07522 100644 --- a/src/compas/datastructures/mesh/smoothing_numpy.py +++ b/src/compas/datastructures/mesh/smoothing_numpy.py @@ -1,6 +1,6 @@ from numpy import array -from compas.datastructures.mesh.core import trimesh_cotangent_laplacian_matrix +from .matrices import trimesh_cotangent_laplacian_matrix __all__ = ['trimesh_smooth_laplacian_cotangent'] diff --git a/src/compas/datastructures/mesh/subdivision.py b/src/compas/datastructures/mesh/subdivision.py index ecd49d10d360..7f2d7eb05f61 100644 --- a/src/compas/datastructures/mesh/subdivision.py +++ b/src/compas/datastructures/mesh/subdivision.py @@ -12,8 +12,6 @@ from compas.utilities import iterable_like from compas.utilities import pairwise -from compas.datastructures.mesh.core import BaseMesh - __all__ = [ 'mesh_subdivide', @@ -27,11 +25,50 @@ ] +def subd_factory(cls): + + class SubdMesh(cls): + + _add_vertex = cls.add_vertex + _add_face = cls.add_face + _insert_vertex = cls.insert_vertex + + def add_vertex(self, x, y, z): + key = self._max_vertex = self._max_vertex + 1 + if key not in self.vertex: + self.vertex[key] = {} + self.halfedge[key] = {} + self.vertex[key] = dict(x=x, y=y, z=z) + return key + + def add_face(self, vertices): + fkey = self._max_face = self._max_face + 1 + self.face[fkey] = vertices + self.facedata[fkey] = {} + for i in range(-1, len(vertices) - 1): + u = vertices[i] + v = vertices[i + 1] + self.halfedge[u][v] = fkey + if u not in self.halfedge[v]: + self.halfedge[v][u] = None + return fkey + + def insert_vertex(self, fkey): + x, y, z = self.face_center(fkey) + w = self.add_vertex(x=x, y=y, z=z) + for u, v in self.face_halfedges(fkey): + self.add_face([u, v, w]) + del self.face[fkey] + return w + + return SubdMesh + + def mesh_fast_copy(other): + SubdMesh = subd_factory(type(other)) subd = SubdMesh() subd.vertex = deepcopy(other.vertex) subd.face = deepcopy(other.face) - # subd.edgedata = deepcopy(other.edgedata) subd.facedata = deepcopy(other.facedata) subd.halfedge = deepcopy(other.halfedge) subd._max_vertex = other._max_vertex @@ -39,50 +76,6 @@ def mesh_fast_copy(other): return subd -class SubdMesh(BaseMesh): - - from compas.datastructures.mesh.core import mesh_split_edge - - _add_vertex = BaseMesh.add_vertex - _add_face = BaseMesh.add_face - _insert_vertex = BaseMesh.insert_vertex - - split_edge = mesh_split_edge - - def add_vertex(self, x, y, z): - key = self._max_vertex = self._max_vertex + 1 - - if key not in self.vertex: - self.vertex[key] = {} - self.halfedge[key] = {} - - self.vertex[key] = dict(x=x, y=y, z=z) - - return key - - def add_face(self, vertices): - fkey = self._max_face = self._max_face + 1 - - self.face[fkey] = vertices - self.facedata[fkey] = {} - - for i in range(-1, len(vertices) - 1): - u = vertices[i] - v = vertices[i + 1] - self.halfedge[u][v] = fkey - if u not in self.halfedge[v]: - self.halfedge[v][u] = None - - return fkey - - def insert_vertex(self, fkey): - x, y, z = self.face_center(fkey) - w = self.add_vertex(x=x, y=y, z=z) - for u, v in self.face_halfedges(fkey): - self.add_face([u, v, w]) - del self.face[fkey] - return w - # distinguish between subd of meshes with and without boundary # closed vs. open # pay attention to extraordinary points @@ -341,6 +334,7 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None): """ cls = type(mesh) + if not fixed: fixed = [] fixed = set(fixed) @@ -495,6 +489,7 @@ def mesh_subdivide_doosabin(mesh, k=1, fixed=None): fixed = set(fixed) cls = type(mesh) + SubdMesh = subd_factory(cls) for _ in range(k): old_xyz = {key: mesh.vertex_coordinates(key) for key in mesh.vertices()} @@ -597,6 +592,7 @@ def mesh_subdivide_frames(mesh, offset, add_windows=False): """ cls = type(mesh) + SubdMesh = subd_factory(cls) subd = SubdMesh() diff --git a/src/compas/datastructures/mesh/triangulation.py b/src/compas/datastructures/mesh/triangulation.py index c8eae043cee7..3cd312c25ccd 100644 --- a/src/compas/datastructures/mesh/triangulation.py +++ b/src/compas/datastructures/mesh/triangulation.py @@ -3,7 +3,7 @@ from __future__ import division -from compas.datastructures.mesh.core import mesh_split_face +from .operations import mesh_split_face __all__ = [ diff --git a/src/compas/datastructures/mesh/trimesh_samplepoints_numpy.py b/src/compas/datastructures/mesh/trimesh_samplepoints_numpy.py index 486a73b44c63..7b145a6e0eff 100644 --- a/src/compas/datastructures/mesh/trimesh_samplepoints_numpy.py +++ b/src/compas/datastructures/mesh/trimesh_samplepoints_numpy.py @@ -1,7 +1,4 @@ -from typing import Tuple, Union - from numpy import array -from numpy import ndarray from numpy.random import choice from numpy.random import rand from numpy import sqrt @@ -11,15 +8,13 @@ from numpy import clip from numpy import finfo -from compas.datastructures.mesh.core import BaseMesh - __all__ = [ 'trimesh_samplepoints_numpy', ] -def trimesh_samplepoints_numpy(mesh: BaseMesh, num_points: int = 1000, return_normals: bool = False) -> Union[ndarray, Tuple[ndarray, ndarray]]: +def trimesh_samplepoints_numpy(mesh, num_points=1000, return_normals=False): """Compute sample points on a triangle mesh surface Parameters diff --git a/src/compas/datastructures/network/__init__.py b/src/compas/datastructures/network/__init__.py index 349da5a13639..135a4d7b84f0 100644 --- a/src/compas/datastructures/network/__init__.py +++ b/src/compas/datastructures/network/__init__.py @@ -2,8 +2,14 @@ from __future__ import division from __future__ import print_function -from .core import * # noqa: F401 F403 -from ._network import * # noqa: F401 F403 +import compas + +from .operations import * # noqa: F401 F403 + +if not compas.IPY: + from .matrices import * # noqa: F401 F403 + +from .network import * # noqa: F401 F403 from .combinatorics import * # noqa: F401 F403 from .complementarity import * # noqa: F401 F403 diff --git a/src/compas/datastructures/network/_network.py b/src/compas/datastructures/network/_network.py deleted file mode 100644 index cfa4e1d1dc97..000000000000 --- a/src/compas/datastructures/network/_network.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -from compas.datastructures.network.core import BaseNetwork -from compas.datastructures.network.core import network_split_edge - -from compas.datastructures.network.combinatorics import network_is_connected -from compas.datastructures.network.complementarity import network_complement -from compas.datastructures.network.transformations import network_transform -from compas.datastructures.network.transformations import network_transformed -from compas.datastructures.network.traversal import network_shortest_path -from compas.datastructures.network.smoothing import network_smooth_centroid - - -__all__ = ['Network'] - - -class Network(BaseNetwork): - - complement = network_complement - is_connected = network_is_connected - shortest_path = network_shortest_path - split_edge = network_split_edge - smooth = network_smooth_centroid - transform = network_transform - transformed = network_transformed diff --git a/src/compas/datastructures/network/core/__init__.py b/src/compas/datastructures/network/core/__init__.py deleted file mode 100644 index 9d1e7556daf4..000000000000 --- a/src/compas/datastructures/network/core/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -from compas import IPY - -from .graph import Graph # noqa: F401 -from .network import BaseNetwork # noqa: F401 - -from .operations import * # noqa: F401 F403 -if not IPY: - from .matrices import * # noqa: F401 F403 - -__all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/compas/datastructures/network/core/matrices.py b/src/compas/datastructures/network/matrices.py similarity index 100% rename from src/compas/datastructures/network/core/matrices.py rename to src/compas/datastructures/network/matrices.py diff --git a/src/compas/datastructures/network/core/network.py b/src/compas/datastructures/network/network.py similarity index 81% rename from src/compas/datastructures/network/core/network.py rename to src/compas/datastructures/network/network.py index a26f2b1cb63e..d3d239d7ef21 100644 --- a/src/compas/datastructures/network/core/network.py +++ b/src/compas/datastructures/network/network.py @@ -4,6 +4,7 @@ import sys import collections +import compas from compas.files import OBJ @@ -16,25 +17,86 @@ from compas.geometry import add_vectors from compas.geometry import scale_vector -from compas.datastructures.network.core import Graph +from compas.datastructures import Graph +from .operations import network_split_edge -__all__ = ['BaseNetwork'] +from .combinatorics import network_is_connected +from .complementarity import network_complement +from .duality import network_find_cycles +from .transformations import network_transform +from .transformations import network_transformed +from .traversal import network_shortest_path +from .smoothing import network_smooth_centroid +from .planarity import network_count_crossings +from .planarity import network_find_crossings +from .planarity import network_is_crossed +from .planarity import network_is_xy -class BaseNetwork(Graph): - """Geometric implementation of a basic edge graph. - Examples - -------- - >>> +class Network(Graph): + """Geometric implementation of an edge graph. + + Parameters + ---------- + name: str, optional + The name of the graph. + Defaults to "Graph". + default_node_attributes: dict, optional + Default values for node attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + """ - def __init__(self): - super(BaseNetwork, self).__init__() - self._max_int_key = -1 - self.attributes.update({'name': 'Network'}) - self.default_node_attributes.update({'x': 0.0, 'y': 0.0, 'z': 0.0}) + complement = network_complement + is_connected = network_is_connected + shortest_path = network_shortest_path + split_edge = network_split_edge + smooth = network_smooth_centroid + transform = network_transform + transformed = network_transformed + find_cycles = network_find_cycles + + count_crossings = network_count_crossings + find_crossings = network_find_crossings + is_crossed = network_is_crossed + is_xy = network_is_xy + + if not compas.IPY: + from .matrices import network_adjacency_matrix + from .matrices import network_connectivity_matrix + from .matrices import network_degree_matrix + from .matrices import network_laplacian_matrix + from .planarity import network_embed_in_plane + from .planarity import network_is_planar + from .planarity import network_is_planar_embedding + + adjacency_matrix = network_adjacency_matrix + connectivity_matrix = network_connectivity_matrix + degree_matrix = network_degree_matrix + laplacian_matrix = network_laplacian_matrix + + embed_in_plane = network_embed_in_plane + is_planar = network_is_planar + is_planar_embedding = network_is_planar_embedding + + def __init__(self, name=None, default_node_attributes=None, default_edge_attributes=None): + name = name or 'Network' + _default_node_attributes = {'x': 0.0, 'y': 0.0, 'z': 0.0} + _default_edge_attributes = {} + if default_node_attributes: + _default_node_attributes.update(default_node_attributes) + if default_edge_attributes: + _default_edge_attributes.update(default_edge_attributes) + super(Network, self).__init__(name=name, + default_node_attributes=_default_node_attributes, + default_edge_attributes=_default_edge_attributes) + + def __str__(self): + tpl = "" + return tpl.format(self.number_of_nodes(), self.number_of_edges()) # -------------------------------------------------------------------------- # customisation @@ -265,16 +327,6 @@ def gkey_key(self, precision=None): # builders # -------------------------------------------------------------------------- - def add_node(self, key=None, attr_dict=None, **kwattr): - if key is None: - key = self._max_int_key = self._max_int_key + 1 - try: - if key > self._max_int_key: - self._max_int_key = key - except (ValueError, TypeError): - pass - return super(BaseNetwork, self).add_node(key, attr_dict=attr_dict, **kwattr) - # -------------------------------------------------------------------------- # modifiers # -------------------------------------------------------------------------- @@ -303,35 +355,6 @@ def add_node(self, key=None, attr_dict=None, **kwattr): # edge topology # -------------------------------------------------------------------------- - # def edge_connected_edges(self, u, v): - # """Return the edges connected to an edge. - - # Parameters - # ---------- - # u : hashable - # The identifier of the first node of the edge. - # v : hashable - # The identifier of the secondt node of the edge. - - # Returns - # ------- - # list - # A list of connected edges. - - # """ - # edges = [] - # for nbr in self.node_neighbors(u): - # if nbr in self.edge[u]: - # edges.append((u, nbr)) - # else: - # edges.append((nbr, u)) - # for nbr in self.node_neighbors(v): - # if nbr in self.edge[v]: - # edges.append((v, nbr)) - # else: - # edges.append((nbr, v)) - # return edges - # -------------------------------------------------------------------------- # node geometry # -------------------------------------------------------------------------- diff --git a/src/compas/datastructures/network/core/operations/__init__.py b/src/compas/datastructures/network/operations/__init__.py similarity index 100% rename from src/compas/datastructures/network/core/operations/__init__.py rename to src/compas/datastructures/network/operations/__init__.py diff --git a/src/compas/datastructures/network/core/operations/join.py b/src/compas/datastructures/network/operations/join.py similarity index 100% rename from src/compas/datastructures/network/core/operations/join.py rename to src/compas/datastructures/network/operations/join.py diff --git a/src/compas/datastructures/network/core/operations/split.py b/src/compas/datastructures/network/operations/split.py similarity index 100% rename from src/compas/datastructures/network/core/operations/split.py rename to src/compas/datastructures/network/operations/split.py diff --git a/src/compas/datastructures/volmesh/__init__.py b/src/compas/datastructures/volmesh/__init__.py index 0a99f1ec42b0..ac867eab1449 100644 --- a/src/compas/datastructures/volmesh/__init__.py +++ b/src/compas/datastructures/volmesh/__init__.py @@ -2,11 +2,12 @@ from __future__ import division from __future__ import print_function -from .core import * # noqa: F401 F403 -from ._volmesh import * # noqa: F401 F403 +from .operations import * # noqa: F401 F403 from .bbox import * # noqa: F401 F403 from .transformations import * # noqa: F401 F403 +from .volmesh import * # noqa: F401 F403 + __all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/compas/datastructures/volmesh/_volmesh.py b/src/compas/datastructures/volmesh/_volmesh.py deleted file mode 100644 index 339191b78e9b..000000000000 --- a/src/compas/datastructures/volmesh/_volmesh.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.datastructures.volmesh.core import BaseVolMesh - -from compas.datastructures.volmesh.bbox import volmesh_bounding_box -from compas.datastructures.volmesh.transformations import volmesh_transform -from compas.datastructures.volmesh.transformations import volmesh_transformed - - -__all__ = ['VolMesh'] - - -class VolMesh(BaseVolMesh): - """Implementation of the base volmesh data structure that adds some of the mesh algorithms as methods. - - """ - - bounding_box = volmesh_bounding_box - transform = volmesh_transform - transformed = volmesh_transformed diff --git a/src/compas/datastructures/volmesh/core/__init__.py b/src/compas/datastructures/volmesh/core/__init__.py deleted file mode 100644 index 60b19806d9df..000000000000 --- a/src/compas/datastructures/volmesh/core/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from .halfface import HalfFace # noqa: F401 -from .volmesh import BaseVolMesh # noqa: F401 - -__all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/compas/datastructures/volmesh/core/attributes.py b/src/compas/datastructures/volmesh/core/attributes.py deleted file mode 100644 index 9990342f8bcb..000000000000 --- a/src/compas/datastructures/volmesh/core/attributes.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.datastructures._mutablemapping import MutableMapping - -__all__ = ['VertexAttributeView', 'EdgeAttributeView', 'FaceAttributeView', 'CellAttributeView'] - - -class AttributeView(object): - """Mixin for attribute dict views.""" - - def __init__(self, defaults, attr, custom_only=False): - super(AttributeView, self).__init__() - self.defaults = defaults - self.attr = attr - self.custom_only = custom_only - - def __str__(self): - s = [] - for k, v in self.items(): - s.append("{}: {}".format(repr(k), repr(v))) - return "{" + ", ".join(s) + "}" - - def __len__(self): - return len(self.defaults) - - def __getitem__(self, name): - if name not in self.attr: - if name not in self.defaults: - raise KeyError - return self.attr.get(name, self.defaults.get(name)) - - def __setitem__(self, name, value): - self.attr[name] = value - - def __delitem__(self, name): - del self.attr[name] - - def __iter__(self): - if self.custom_only: - for name in self.attr: - yield name - else: - for name in self.defaults: - yield name - - -class VertexAttributeView(AttributeView, MutableMapping): - """Mutable Mapping that provides a read/write view of the custom attributes of a vertex - combined with the default attributes of all vertices.""" - - def __init__(self, defaults, attr, custom_only=False): - super(VertexAttributeView, self).__init__(defaults, attr, custom_only) - - -class EdgeAttributeView(AttributeView, MutableMapping): - """Mutable Mapping that provides a read/write view of the custom attributes of an edge - combined with the default attributes of all edges.""" - - def __init__(self, defaults, attr, custom_only=False): - super(EdgeAttributeView, self).__init__(defaults, attr, custom_only) - - -class FaceAttributeView(AttributeView, MutableMapping): - """Mutable Mapping that provides a read/write view of the custom attributes of a halfface - combined with the default attributes of all faces.""" - - def __init__(self, defaults, attr, custom_only=False): - super(FaceAttributeView, self).__init__(defaults, attr, custom_only) - - -class CellAttributeView(AttributeView, MutableMapping): - """Mutable Mapping that provides a read/write view of the custom attributes of a cell - combined with the default attributes of all faces.""" - - def __init__(self, defaults, attr, custom_only=False): - super(CellAttributeView, self).__init__(defaults, attr, custom_only) diff --git a/src/compas/datastructures/volmesh/core/operations/__init__.py b/src/compas/datastructures/volmesh/operations/__init__.py similarity index 100% rename from src/compas/datastructures/volmesh/core/operations/__init__.py rename to src/compas/datastructures/volmesh/operations/__init__.py diff --git a/src/compas/datastructures/volmesh/core/volmesh.py b/src/compas/datastructures/volmesh/volmesh.py similarity index 88% rename from src/compas/datastructures/volmesh/core/volmesh.py rename to src/compas/datastructures/volmesh/volmesh.py index 5445691ab737..2c89013e629b 100644 --- a/src/compas/datastructures/volmesh/core/volmesh.py +++ b/src/compas/datastructures/volmesh/volmesh.py @@ -2,7 +2,7 @@ from __future__ import division from __future__ import print_function -from compas.datastructures.volmesh.core.halfface import HalfFace +from compas.datastructures import HalfFace from compas.datastructures import Mesh from compas.files import OBJ @@ -22,61 +22,65 @@ from compas.utilities import geometric_key +from .bbox import volmesh_bounding_box +from .transformations import volmesh_transform +from .transformations import volmesh_transformed -__all__ = ['BaseVolMesh'] +__all__ = ['VolMesh'] -class BaseVolMesh(HalfFace): + +class VolMesh(HalfFace): """Geometric implementation of a face data structure for volumetric meshes. - Attributes + Parameters ---------- - attributes : dict - A dictionary of general volmesh attributes. - - * ``'name': "VolMesh"`` - - default_vertex_attributes : dict - The names of pre-assigned vertex attributes and their default values. - - * ``'x': 0.0`` - * ``'y': 0.0`` - * ``'z': 0.0`` - - default_edge_attributes : dict - The default data attributes assigned to every new edge. - default_face_attributes : dict - The default data attributes assigned to every new face. - name : str - The name of the volmesh. - Shorthand for ``volmesh.attributes['name']`` - - data : dict - The data representing the mesh. - The dict has the following structure: - - * 'attributes' => dict - * 'dva' => dict - * 'dea' => dict - * 'dfa' => dict - * 'dca' => dict - * 'vertex' => dict - * 'halface' => dict - * 'cell' => dict - * 'plane' => dict - * 'edgedata' => dict - * 'facedata' => dict - * 'celldata' => dict - * 'max_int_key' => int - * 'max_int_hfkey' => int - * 'max_int_ckey' => int + name: str, optional + The name of the graph. + Defaults to "Graph". + default_vertex_attributes: dict, optional + Default values for vertex attributes. + default_edge_attributes: dict, optional + Default values for edge attributes. + default_face_attributes: dict, optional + Default values for face attributes. + default_cell_attributes: dict, optional + Default values for cell attributes. """ - def __init__(self): - super(BaseVolMesh, self).__init__() - self.attributes.update({'name': 'VolMesh'}) - self.default_vertex_attributes.update({'x': 0.0, 'y': 0.0, 'z': 0.0}) + bounding_box = volmesh_bounding_box + transform = volmesh_transform + transformed = volmesh_transformed + + def __init__(self, + name=None, + default_vertex_attributes=None, + default_edge_attributes=None, + default_face_attributes=None, + default_cell_attributes=None): + name = name or 'VolMesh' + _default_vertex_attributes = {'x': 0.0, 'y': 0.0, 'z': 0.0} + _default_edge_attributes = {} + _default_face_attributes = {} + _default_cell_attributes = {} + if default_vertex_attributes: + _default_vertex_attributes.update(default_vertex_attributes) + if default_edge_attributes: + _default_edge_attributes.update(default_edge_attributes) + if default_face_attributes: + _default_face_attributes.update(default_face_attributes) + if default_cell_attributes: + _default_cell_attributes.update(default_cell_attributes) + super(VolMesh, self).__init__(name=name, + default_vertex_attributes=_default_vertex_attributes, + default_edge_attributes=_default_edge_attributes, + default_face_attributes=_default_face_attributes, + default_cell_attributes=_default_cell_attributes) + + def __str__(self): + tpl = "" + return tpl.format(self.number_of_vertices(), self.number_of_faces(), self.number_of_cells(), self.number_of_edges()) # -------------------------------------------------------------------------- # customisation diff --git a/src/compas/utilities/__init__.py b/src/compas/utilities/__init__.py index b6eae6dadeb6..b8bc9a0ed08d 100644 --- a/src/compas/utilities/__init__.py +++ b/src/compas/utilities/__init__.py @@ -122,7 +122,7 @@ abstractclassmethod, abstractstaticmethod, memoize, - print_profile + print_profile, ) from .descriptors import ( Float, diff --git a/src/compas/utilities/decorators.py b/src/compas/utilities/decorators.py index be6825418d4b..71772b56b5a1 100644 --- a/src/compas/utilities/decorators.py +++ b/src/compas/utilities/decorators.py @@ -25,7 +25,7 @@ 'abstractstaticmethod', 'abstractclassmethod', 'memoize', - 'print_profile' + 'print_profile', ] diff --git a/tests/compas/datastructures/test_graph.py b/tests/compas/datastructures/test_graph.py index 5bc1eb249e7a..d4e3fc94ca86 100644 --- a/tests/compas/datastructures/test_graph.py +++ b/tests/compas/datastructures/test_graph.py @@ -52,6 +52,34 @@ def test_graph_json_schema(graph): graph.validate_json() +# ============================================================================== +# Tests - Attributes +# ============================================================================== + + +def test_default_node_attributes(): + graph = Graph(name='test', default_node_attributes={'a': 1, 'b': 2}) + for node in graph.nodes(): + assert graph.node_attribute(node, name='a') == 1 + assert graph.node_attribute(node, name='b') == 2 + graph.node_attribute(node, name='a', value=3) + assert graph.node_attribute(node, name='a') == 3 + + +def test_default_edge_attributes(): + graph = Graph(name='test', default_edge_attributes={'a': 1, 'b': 2}) + for edge in graph.edges(): + assert graph.edge_attribute(edge, name='a') == 1 + assert graph.edge_attribute(edge, name='b') == 2 + graph.edge_attribute(edge, name='a', value=3) + assert graph.edge_attribute(edge, name='a') == 3 + + +# ============================================================================== +# Tests - Conversion +# ============================================================================== + + def test_graph_networkx_conversion(): if compas.IPY: return diff --git a/tests/compas/datastructures/test_halfedge.py b/tests/compas/datastructures/test_halfedge.py index ce60ee293ccd..f7d4fb943f0a 100644 --- a/tests/compas/datastructures/test_halfedge.py +++ b/tests/compas/datastructures/test_halfedge.py @@ -70,6 +70,15 @@ def test_json_schema(mesh): # ============================================================================== +def test_default_vertex_attributes(): + he = HalfEdge(name='test', default_vertex_attributes={'a': 1, 'b': 2}) + for vertex in he.vertices(): + assert he.vertex_attribute(vertex, name='a') == 1 + assert he.vertex_attribute(vertex, name='b') == 2 + he.vertex_attribute(vertex, name='a', value=3) + assert he.vertex_attribute(vertex, name='a') == 3 + + def test_vertex_attributes_key_not_found(mesh): with pytest.raises(KeyError): mesh.vertex_attributes(mesh.number_of_vertices() + 1) @@ -111,6 +120,16 @@ def test_del_vertex_attribute_in_view(mesh, vertex_key): # Tests - Face Attributes # ============================================================================== + +def test_default_face_attributes(): + he = HalfEdge(name='test', default_face_attributes={'a': 1, 'b': 2}) + for face in he.vertices(): + assert he.face_attribute(face, name='a') == 1 + assert he.face_attribute(face, name='b') == 2 + he.face_attribute(face, name='a', value=3) + assert he.face_attribute(face, name='a') == 3 + + def test_face_attributes_is_empty(mesh): assert mesh.face_attributes(mesh.get_any_face()) == {} @@ -151,6 +170,16 @@ def test_del_face_attribute_in_view(mesh, face_key): # Tests - Edge Attributes # ============================================================================== + +def test_default_edge_attributes(): + he = HalfEdge(name='test', default_edge_attributes={'a': 1, 'b': 2}) + for edge in he.vertices(): + assert he.edge_attribute(edge, name='a') == 1 + assert he.edge_attribute(edge, name='b') == 2 + he.edge_attribute(edge, name='a', value=3) + assert he.edge_attribute(edge, name='a') == 3 + + def test_edge_attributes_is_empty(mesh, edge_key): assert mesh.edge_attributes(edge_key) == {} diff --git a/tests/compas/datastructures/test_halfface.py b/tests/compas/datastructures/test_halfface.py new file mode 100644 index 000000000000..525f6a7961bc --- /dev/null +++ b/tests/compas/datastructures/test_halfface.py @@ -0,0 +1,65 @@ +from compas.datastructures import HalfFace + + +# ============================================================================== +# Fixtures +# ============================================================================== + +# ============================================================================== +# Tests - Schema & jsonschema +# ============================================================================== + +# ============================================================================== +# Tests - Vertex Attributes +# ============================================================================== + + +def test_default_vertex_attributes(): + he = HalfFace(name='test', default_vertex_attributes={'a': 1, 'b': 2}) + for vertex in he.vertices(): + assert he.vertex_attribute(vertex, name='a') == 1 + assert he.vertex_attribute(vertex, name='b') == 2 + he.vertex_attribute(vertex, name='a', value=3) + assert he.vertex_attribute(vertex, name='a') == 3 + + +# ============================================================================== +# Tests - Face Attributes +# ============================================================================== + + +def test_default_face_attributes(): + he = HalfFace(name='test', default_face_attributes={'a': 1, 'b': 2}) + for face in he.vertices(): + assert he.face_attribute(face, name='a') == 1 + assert he.face_attribute(face, name='b') == 2 + he.face_attribute(face, name='a', value=3) + assert he.face_attribute(face, name='a') == 3 + + +# ============================================================================== +# Tests - Edge Attributes +# ============================================================================== + + +def test_default_edge_attributes(): + he = HalfFace(name='test', default_edge_attributes={'a': 1, 'b': 2}) + for edge in he.vertices(): + assert he.edge_attribute(edge, name='a') == 1 + assert he.edge_attribute(edge, name='b') == 2 + he.edge_attribute(edge, name='a', value=3) + assert he.edge_attribute(edge, name='a') == 3 + + +# ============================================================================== +# Tests - Cell Attributes +# ============================================================================== + + +def test_default_cell_attributes(): + he = HalfFace(name='test', default_cell_attributes={'a': 1, 'b': 2}) + for cell in he.vertices(): + assert he.cell_attribute(cell, name='a') == 1 + assert he.cell_attribute(cell, name='b') == 2 + he.cell_attribute(cell, name='a', value=3) + assert he.cell_attribute(cell, name='a') == 3