halfedge 0.6.0__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,13 +23,15 @@ then passing that raw data to mesh_from_vr would create a mesh with 6 faces and
23
23
  from __future__ import annotations
24
24
 
25
25
  from contextlib import suppress
26
- from typing import TYPE_CHECKING, Any, Iterable, Sequence, TypeVar
26
+ from typing import TYPE_CHECKING, Any, TypeVar
27
27
 
28
28
  from paragraphs import par
29
29
 
30
30
  from halfedge.half_edge_elements import Edge, Face, ManifoldMeshError, Vert
31
31
 
32
32
  if TYPE_CHECKING:
33
+ from collections.abc import Iterable, Sequence
34
+
33
35
  from halfedge.type_attrib import Attrib, StaticAttrib
34
36
 
35
37
 
@@ -84,11 +84,31 @@ class MeshElementBase:
84
84
  """
85
85
  self.sn = next(self._sn_generator)
86
86
  self.attrib: dict[str, Attrib[Any]] = {}
87
- self.mesh = mesh
87
+ self._mesh = mesh
88
88
 
89
89
  for attribute in attributes:
90
90
  self.set_attrib(attribute)
91
91
 
92
+ @property
93
+ def mesh(self) -> BlindHalfEdges:
94
+ """Return the mesh instance.
95
+
96
+ :return: the mesh instance
97
+ :raise AttributeError: if mesh not set for self
98
+ """
99
+ if self._mesh is not None:
100
+ return self._mesh
101
+ msg = "mesh not set for self"
102
+ raise AttributeError(msg)
103
+
104
+ @mesh.setter
105
+ def mesh(self, mesh: BlindHalfEdges) -> None:
106
+ """Set the mesh instance.
107
+
108
+ :param mesh: the mesh instance
109
+ """
110
+ self._mesh = mesh
111
+
92
112
  def set_attrib(self, attrib: Attrib[Any]) -> None:
93
113
  """Set an attribute.
94
114
 
@@ -79,6 +79,13 @@ class HalfEdges(StaticHalfEdges):
79
79
  Able to infer from:
80
80
  * both verts on same face: that face
81
81
  * empty mesh: a new Hole
82
+
83
+ This is only called when inserting an edge into an existing face (or empty
84
+ mesh), so the orig and dest should always be connected to only one face (or
85
+ nothing, which is handled in this method).
86
+
87
+ Once the face is split, the two verts will form an edge that connects to two
88
+ faces.
82
89
  """
83
90
  if not self.edges:
84
91
  return self.new_hole()
halfedge/type_attrib.py CHANGED
@@ -61,7 +61,7 @@ When assigned to a Vert instance, these will be stored in the Vert instance's
61
61
  from __future__ import annotations
62
62
 
63
63
  from contextlib import suppress
64
- from typing import TYPE_CHECKING, Any, Generic, Literal, Tuple, TypeVar
64
+ from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
65
65
 
66
66
  from paragraphs import par
67
67
 
@@ -79,7 +79,7 @@ class StaticAttrib(Generic[_T]):
79
79
  merged or split.
80
80
  """
81
81
 
82
- __slots__ = ("_value", "_mesh")
82
+ __slots__ = ("_mesh", "_value")
83
83
 
84
84
  def __new__(
85
85
  cls: type[_TStaticAttrib],
@@ -193,7 +193,7 @@ class Attrib(Generic[_T]):
193
193
  every case.
194
194
  """
195
195
 
196
- __slots__ = ("_value", "_element")
196
+ __slots__ = ("_element", "_value")
197
197
 
198
198
  def __new__(
199
199
  cls: type[_TAttrib],
@@ -469,7 +469,7 @@ class NumericAttrib(Attrib[_T]):
469
469
  return type(have_values[0])(sum(values) / len(values))
470
470
 
471
471
 
472
- class Vector2Attrib(Attrib[Tuple[float, float]]):
472
+ class Vector2Attrib(Attrib[tuple[float, float]]):
473
473
  """Average merge_from values as xy tuples."""
474
474
 
475
475
  def __new__(
@@ -501,7 +501,7 @@ class Vector2Attrib(Attrib[Tuple[float, float]]):
501
501
  return type(have_values[0])((sum_x / num, sum_y / num))
502
502
 
503
503
 
504
- class Vector3Attrib(Attrib[Tuple[float, float, float]]):
504
+ class Vector3Attrib(Attrib[tuple[float, float, float]]):
505
505
  """Average merge_from values as xyz tuples."""
506
506
 
507
507
  def __new__(
halfedge/validations.py CHANGED
@@ -10,11 +10,13 @@ created: 181127
10
10
  from __future__ import annotations
11
11
 
12
12
  from itertools import chain
13
- from typing import TYPE_CHECKING, Any, Callable, Iterator, TypeVar
13
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
14
14
 
15
15
  from halfedge.half_edge_elements import Edge, Face, ManifoldMeshError
16
16
 
17
17
  if TYPE_CHECKING:
18
+ from collections.abc import Iterator
19
+
18
20
  from halfedge.half_edge_querries import StaticHalfEdges
19
21
 
20
22
  _T = TypeVar("_T")
@@ -1,19 +1,19 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: halfedge
3
- Version: 0.6.0
3
+ Version: 0.8.1
4
4
  Summary: A typical half-edge data structure with some padding
5
5
  Author-email: Shay Hill <shay_public@hotmail.com>
6
6
  License: MIT
7
- Requires-Python: >=3.8
7
+ Requires-Python: >=3.9
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: paragraphs
10
10
  Provides-Extra: dev
11
- Requires-Dist: commitizen ; extra == 'dev'
12
- Requires-Dist: coverage ; extra == 'dev'
13
- Requires-Dist: pre-commit ; extra == 'dev'
14
- Requires-Dist: pylint ; extra == 'dev'
15
- Requires-Dist: pytest ; extra == 'dev'
16
- Requires-Dist: tox ; extra == 'dev'
11
+ Requires-Dist: commitizen; extra == "dev"
12
+ Requires-Dist: coverage; extra == "dev"
13
+ Requires-Dist: pre-commit; extra == "dev"
14
+ Requires-Dist: pylint; extra == "dev"
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: tox; extra == "dev"
17
17
 
18
18
  # A typical halfedges data structure with some padding
19
19
 
@@ -35,11 +35,11 @@ mesh.remove_edge(edge)
35
35
 
36
36
  ## Particulars
37
37
 
38
- The idea (for 18 years and counting) has been to create an interface that is neither too complex, too verbose, nor too magical. I've been all over the place as to where that line should be. This is my current thinking.
38
+ The idea (for 19 years and counting) has been to create an interface that is neither too complex, too verbose, nor too magical. I've been all over the place as to where that line should be. This is my current thinking.
39
39
 
40
40
  ### Reflection
41
41
 
42
- If you set `edge_a.orig = vert_a`, then the `Edge.vert` setter will *automagically* set `vert_a.edge = edge_a`. This is true for any setter that might otherwise break the mesh. Sometimes, this reflection will happen when it isn't strictly necessary. Imagine you have `face_a` with three edges: `edge_a`, `edge_b` and `edge_c`. `face_a` has a pointer to `edge_a`, but it could point to any of the three edges and still be correct per the requirements of the halfedge data structure. If you directly set `edge_b.face = face_a`, everything would still be correct (`face_a.edge` would still be `edge_a` and that would still be correct), but the `Edge.edge` setter will nevertheless set `face_a.edge = edge_b`.
42
+ If you set `edge_a.orig = vert_a`, then the `Edge.vert` setter will *automagically* set `vert_a.edge = edge_a`. This is true for any setter that might otherwise break the mesh. Sometimes, this reflection will happen when it isn't strictly necessary. Imagine you have `face_a` with three edges: `edge_a`, `edge_b` and `edge_c`. `face_a` has a pointer to `edge_a`, but it could point to any of the three edges and still be correct per the requirements of the halfedge data structure. If you directly set `edge_b.face = face_a`, everything would still be correct (`face_a.edge` would still be `edge_a` and that would still be correct), but the `Edge.edge` setter will nevertheless "reflectively" set `face_a.edge = edge_b`.
43
43
 
44
44
  ### id
45
45
 
@@ -64,50 +64,66 @@ Face(
64
64
 
65
65
  The `is_hole` `__init__` kwarg is shorthand for
66
66
 
67
- class IsHole(ContagionAttribute):
68
- pass
67
+ ```python
68
+ class IsHole(ContagionAttribute):
69
+ pass
70
+
71
+ face = Face()
72
+ face.add_attrib(IsHole())
73
+ ```
69
74
 
70
- vert = Vert()
71
- vert.add_attrib(IsHole())
72
75
 
73
76
  The `face_instance.is_hole` property getter is shorthand for
74
77
 
75
- vert.get_attrib(IsHole())
78
+ ```python
79
+ vert.get_attrib(IsHole())
80
+ ```
76
81
 
77
82
  More on `Attrib` classes below and in `type_attrib.py`.
78
83
 
79
84
  ### Element Attributes
80
85
 
81
- By halfedge convention, each Vert instance holds a pointer to one Edge instance, each Face instance holds a pointer to one Edge instance, and each Edge instance holds four pointers (orig, pair, face, next). These describe the geometry of a mesh, but there may be other attributes you would like to assign to these instances. For example, each Face instance might have a color or each Vert instance an (x, y) coordinate. There is no objectively correct way define these attributes or to combine them when two elements are merged. If you assign position vertices to your verts, will they be xy tuples or numpy arrays? Do a red and a blue face behave like paint and combine to make a purple face? Or do they behave like DNA to make a red *or* blue face depending on which is dominant?
86
+ By halfedge convention
82
87
 
83
- These cannot be stored as simple attributes (e.g., `face.color`), because it wouldn't be clear what to do when two faces were combined--by, for instance, deleting a shared edge. Somewhere, you have to define a rule for how different colored faces merge or how coordinate locations combine when merging verts. So, properties like color must be defined here as `Attrib` instances. To create an attribute, inherit from `Attrib` or one of its children defined in `type_attrib.py`. Define `merge`, `split`, and `_infer_value` methods to determine how (for instance, face color) will behave when merged or cached.
88
+ - each Vert instance holds a pointer to one Edge instance;
89
+ - each Face instance holds a pointer to one Edge instance; and
90
+ - each Edge instance holds four pointers (orig, pair, face, next).
84
91
 
85
- You cannot assign these with `instance.attribute`. Instead assign with `vert.add_attrib(attrib_instance)`. This will add the Attrib to a `attrib` dict in Vert instance dict. Retrieve the value with `vert_instance.get_attrib(attrib_class)`. Everything will be keyed to the class name, so you will need a new ElemAttribBase descendant for each attribute type.
92
+ These describe the geometry of a mesh, but there may be other attributes you would like to assign to these instances. For example, each Face instance might have a color. There is no objectively correct way to define a face color, nor to merge colors when two faces are merged, nor to split a color when faces are split. Do a red and a blue face behave like paint and combine to make a purple face? Or do they behave like DNA to make a red *or* blue face depending on which is dominant? This library will not guess.
86
93
 
87
- class Coordinate(IncompatibleAttribute[Tuple[float, float]]):
88
- pass
94
+ For each such attribute, you will need to define `merge` and `split` methods to explicate how the attribute 1) combines when elements are merged; and 2) behaves when an element is split. Do this by creating a new descendent of `Attrib` or one of `Attrib`'s children defined in `type_attrib.py`. See the docstring in that file for more information.
89
95
 
90
- vert = Vert()
91
- vert.add_attrib(Coordinate((1, 2)))
92
- assert vert.get_attrib(Coordinate).value == (1, 2)
96
+ ```python
97
+ # `Vector2Attrib` is a child of `Attrib` defined in `type_attrib.py`.
98
+ # It defines suitable (YMMV) `merge` (average) and `split` (fail) methods for
99
+ # an (x, y) coordinate.
100
+ class Coordinate(Vector2Attrib):
101
+ pass
102
+
103
+ vert = Vert()
104
+ vert.add_attrib(Coordinate((1, 2)))
105
+ assert vert.get_attrib(Coordinate).value == (1, 2)
106
+ ```
93
107
 
94
- These element attributes can also be passed at `__init__`
108
+ You cannot assign or access these attributes with `vert.attribute`. Instead assign with `vert.add_attrib(attrib_instance)`. Retrieve the value with `vert.get_attrib(attrib_class)`. Everything will be keyed to the class name, so you will need a new ElemAttribBase descendant for each attribute type.
95
109
 
96
- vert = Vert(Coordinate(1, 2))
97
- assert vert.get_attrib(Coordinate).value == (1, 2)
110
+ These element attributes can also be passed at `__init__`
98
111
 
99
- The Attrib classes and merge and split methods that will be called when two elements are merged (e.g., merge two faces when removing the edge between them) or split (e.g., split an edge into two edges).
112
+ ```python
113
+ vert = Vert(Coordinate(1, 2))
114
+ assert vert.get_attrib(Coordinate).value == (1, 2)
115
+ ```
100
116
 
101
117
  ### You Should Know
102
118
 
103
119
  A canonical half-edge data structure stores:
104
120
 
105
- * a set of verts (redundant)
106
- * for each vert, a pointer to an edge
107
- * a set of edges
108
- * for each edge, pointers to vert, pair, face, next
109
- * a set of faces (redundant)
110
- * for each face, a pointer to an edge
121
+ - a set of verts (redundant)
122
+ - for each vert, a pointer to an edge
123
+ - a set of edges
124
+ - for each edge, pointers to vert, pair, face, next
125
+ - a set of faces (redundant)
126
+ - for each face, a pointer to an edge
111
127
 
112
128
  This implementation only stores a set of edges. Sets of verts and faces are generated by iterating through references in edge instances. This makes for slower code, but does not violate DRY and makes for dramatically cleaner code.
113
129
 
@@ -0,0 +1,12 @@
1
+ halfedge/__init__.py,sha256=O5tfw3JIBJH83JlpwdYoTNHFsmcg959rAJd6RU51E4s,661
2
+ halfedge/half_edge_constructors.py,sha256=0V9xsjPgoA0zKuqUSSLJI1WKhLDJAXHzVWwH2AHZcq4,8686
3
+ halfedge/half_edge_elements.py,sha256=oX4ti6f6L1gcYsoP-KGNwdF4-cRpTJeBoMGMTJuIN14,18554
4
+ halfedge/half_edge_object.py,sha256=A70CVmV44zDi46gbtGHj8dw4Ltshqs56MRn5-78eM20,28771
5
+ halfedge/half_edge_querries.py,sha256=q5xVOFdpw7t6fhs-Z8yF5j3RlG4DOrOgoLbBsAfkm4A,5179
6
+ halfedge/py.typed,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
7
+ halfedge/type_attrib.py,sha256=NP8h5DmkSeq_v-wfueUumFeUKY94XqDoTQR9Lu8n094,20413
8
+ halfedge/validations.py,sha256=8dDUf76t1_63_e0V5tRETHJDoYUUmjnSFgsULwSSKP4,4544
9
+ halfedge-0.8.1.dist-info/METADATA,sha256=HITY6W4YbbBLTaGm68Iizy9tDtaMxmDyXj5gyYp4PKo,6996
10
+ halfedge-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ halfedge-0.8.1.dist-info/top_level.txt,sha256=iqphKHiIR4DfFMs14-gkFOWTXVkRWfGmDZmCYHhuoFM,9
12
+ halfedge-0.8.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- halfedge/__init__.py,sha256=O5tfw3JIBJH83JlpwdYoTNHFsmcg959rAJd6RU51E4s,661
2
- halfedge/half_edge_constructors.py,sha256=ToGcOYW2b848ynlY59xRNd7ibZuu3OqQpauPCl-N1YE,8654
3
- halfedge/half_edge_elements.py,sha256=6PkPjGxe_0FYSWXi5MY8Yp4an7yYMWir8MPZn8QlUOc,18042
4
- halfedge/half_edge_object.py,sha256=Cc1t_hy2tF52O9iD5tvZN6_w4H7Wt9hwDGFLdBqUxr4,28451
5
- halfedge/half_edge_querries.py,sha256=q5xVOFdpw7t6fhs-Z8yF5j3RlG4DOrOgoLbBsAfkm4A,5179
6
- halfedge/py.typed,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
7
- halfedge/type_attrib.py,sha256=1W06nlyTSc0Q6f6PnQec_VNNRLVSgdNStoBlJmMgJNQ,20420
8
- halfedge/validations.py,sha256=FhUv_J7XcaAduyudYGmi7ZL1tEqwJoCxKZwJU2zQolg,4512
9
- halfedge-0.6.0.dist-info/METADATA,sha256=G5Ee4xaYWSU7gN4BXPuSjquALcOBy3-xWAsxxvmBa-c,7411
10
- halfedge-0.6.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
11
- halfedge-0.6.0.dist-info/top_level.txt,sha256=iqphKHiIR4DfFMs14-gkFOWTXVkRWfGmDZmCYHhuoFM,9
12
- halfedge-0.6.0.dist-info/RECORD,,