halfedge 0.6.0__tar.gz → 0.8.1__tar.gz

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.
Files changed (28) hide show
  1. {halfedge-0.6.0 → halfedge-0.8.1}/.github/workflows/pypi-project.yml +1 -1
  2. {halfedge-0.6.0 → halfedge-0.8.1}/.pre-commit-config.yaml +12 -15
  3. {halfedge-0.6.0/src/halfedge.egg-info → halfedge-0.8.1}/PKG-INFO +44 -28
  4. {halfedge-0.6.0 → halfedge-0.8.1}/README.md +41 -25
  5. {halfedge-0.6.0 → halfedge-0.8.1}/pyproject.toml +5 -5
  6. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/half_edge_constructors.py +3 -1
  7. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/half_edge_elements.py +21 -1
  8. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/half_edge_object.py +7 -0
  9. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/type_attrib.py +5 -5
  10. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/validations.py +3 -1
  11. {halfedge-0.6.0 → halfedge-0.8.1/src/halfedge.egg-info}/PKG-INFO +44 -28
  12. {halfedge-0.6.0 → halfedge-0.8.1}/tests/conftest.py +25 -24
  13. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_classes.py +2 -4
  14. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_constructors.py +17 -17
  15. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_elements.py +3 -5
  16. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_operations.py +3 -3
  17. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_validations.py +1 -3
  18. {halfedge-0.6.0 → halfedge-0.8.1}/.gitignore +0 -0
  19. {halfedge-0.6.0 → halfedge-0.8.1}/setup.cfg +0 -0
  20. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/__init__.py +0 -0
  21. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/half_edge_querries.py +0 -0
  22. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge/py.typed +0 -0
  23. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge.egg-info/SOURCES.txt +0 -0
  24. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge.egg-info/dependency_links.txt +0 -0
  25. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge.egg-info/requires.txt +0 -0
  26. {halfedge-0.6.0 → halfedge-0.8.1}/src/halfedge.egg-info/top_level.txt +0 -0
  27. {halfedge-0.6.0 → halfedge-0.8.1}/tests/__init__.py +0 -0
  28. {halfedge-0.6.0 → halfedge-0.8.1}/tests/test_object_pickups.py +0 -0
@@ -17,7 +17,7 @@ jobs:
17
17
  strategy:
18
18
  fail-fast: false
19
19
  matrix:
20
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
20
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
21
21
  os: [ubuntu-latest, macos-latest, windows-latest]
22
22
  # if: startsWith(github.event.head_commit.message, 'bump:') == false
23
23
  steps:
@@ -4,7 +4,7 @@ ci:
4
4
  repos:
5
5
 
6
6
  - repo: https://github.com/pre-commit/pre-commit-hooks
7
- rev: v4.6.0
7
+ rev: v6.0.0
8
8
  hooks:
9
9
  - id: check-added-large-files
10
10
  - id: check-ast
@@ -29,9 +29,6 @@ repos:
29
29
  - id: mixed-line-ending
30
30
  - id: requirements-txt-fixer
31
31
  - id: trailing-whitespace
32
- - id: fix-encoding-pragma
33
- args:
34
- - --remove
35
32
  - id: name-tests-test
36
33
  args:
37
34
  - --pytest-test-first
@@ -42,12 +39,12 @@ repos:
42
39
  # files: .pre-commit-config.yaml
43
40
 
44
41
  - repo: https://github.com/pre-commit/mirrors-mypy
45
- rev: v1.11.1
42
+ rev: v1.17.1
46
43
  hooks:
47
44
  - id: mypy
48
45
  name: mypy
49
46
  language: python
50
- language_version: python 3.12
47
+ language_version: python 3.9
51
48
  types: [python]
52
49
  require_serial: true
53
50
  verbose: true
@@ -58,23 +55,23 @@ repos:
58
55
  # files: ^(src/|tests/)
59
56
 
60
57
  - repo: https://github.com/PyCQA/isort
61
- rev: 5.13.2
58
+ rev: 6.0.1
62
59
  hooks:
63
60
  - id: isort
64
61
  args: ["--profile", "black", "--filter-files", "--combine-as", "honor--noqa"]
65
62
 
66
63
  - repo: https://github.com/psf/black
67
- rev: 24.8.0
64
+ rev: 25.1.0
68
65
  hooks:
69
66
  - id: black
70
- language_version: python3.8
67
+ language_version: python3.9
71
68
  args: ["--skip-magic-trailing-comma"]
72
69
 
73
70
  - repo: https://github.com/asottile/pyupgrade
74
- rev: v3.17.0
71
+ rev: v3.20.0
75
72
  hooks:
76
73
  - args:
77
- - --py38-plus
74
+ - --py39-plus
78
75
  id: pyupgrade
79
76
 
80
77
  - repo: https://github.com/Lucas-C/pre-commit-hooks
@@ -83,7 +80,7 @@ repos:
83
80
  - id: remove-tabs
84
81
 
85
82
  - repo: https://github.com/commitizen-tools/commitizen
86
- rev: v3.28.0
83
+ rev: v4.8.3
87
84
  hooks:
88
85
  - id: commitizen
89
86
 
@@ -129,18 +126,18 @@ repos:
129
126
  # PLR2004 magic value used in comparison
130
127
  # D403 First word of the first line should be properly capitalized
131
128
  # PLR0913 too namy arguments
132
- rev: 'v0.5.7'
129
+ rev: 'v0.12.10'
133
130
  hooks:
134
131
  - id: ruff
135
132
  exclude: "tests"
136
133
  args:
137
- - --target-version=py38
134
+ - --target-version=py39
138
135
  - --select=ALL
139
136
  - --ignore=COM812,D203,D213,ISC003,PYI019,A002,PLR2004,D403,PLR0913
140
137
  # - --fix
141
138
 
142
139
  # reads pyproject.toml for additional config
143
140
  - repo: https://github.com/RobertCraigie/pyright-python
144
- rev: v1.1.375
141
+ rev: v1.1.404
145
142
  hooks:
146
143
  - id: pyright
@@ -1,10 +1,10 @@
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
@@ -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
 
@@ -18,11 +18,11 @@ mesh.remove_edge(edge)
18
18
 
19
19
  ## Particulars
20
20
 
21
- 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.
21
+ 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.
22
22
 
23
23
  ### Reflection
24
24
 
25
- 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`.
25
+ 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`.
26
26
 
27
27
  ### id
28
28
 
@@ -47,50 +47,66 @@ Face(
47
47
 
48
48
  The `is_hole` `__init__` kwarg is shorthand for
49
49
 
50
- class IsHole(ContagionAttribute):
51
- pass
50
+ ```python
51
+ class IsHole(ContagionAttribute):
52
+ pass
53
+
54
+ face = Face()
55
+ face.add_attrib(IsHole())
56
+ ```
52
57
 
53
- vert = Vert()
54
- vert.add_attrib(IsHole())
55
58
 
56
59
  The `face_instance.is_hole` property getter is shorthand for
57
60
 
58
- vert.get_attrib(IsHole())
61
+ ```python
62
+ vert.get_attrib(IsHole())
63
+ ```
59
64
 
60
65
  More on `Attrib` classes below and in `type_attrib.py`.
61
66
 
62
67
  ### Element Attributes
63
68
 
64
- 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?
69
+ By halfedge convention
65
70
 
66
- 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.
71
+ - each Vert instance holds a pointer to one Edge instance;
72
+ - each Face instance holds a pointer to one Edge instance; and
73
+ - each Edge instance holds four pointers (orig, pair, face, next).
67
74
 
68
- 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.
75
+ 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.
69
76
 
70
- class Coordinate(IncompatibleAttribute[Tuple[float, float]]):
71
- pass
77
+ 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.
72
78
 
73
- vert = Vert()
74
- vert.add_attrib(Coordinate((1, 2)))
75
- assert vert.get_attrib(Coordinate).value == (1, 2)
79
+ ```python
80
+ # `Vector2Attrib` is a child of `Attrib` defined in `type_attrib.py`.
81
+ # It defines suitable (YMMV) `merge` (average) and `split` (fail) methods for
82
+ # an (x, y) coordinate.
83
+ class Coordinate(Vector2Attrib):
84
+ pass
85
+
86
+ vert = Vert()
87
+ vert.add_attrib(Coordinate((1, 2)))
88
+ assert vert.get_attrib(Coordinate).value == (1, 2)
89
+ ```
76
90
 
77
- These element attributes can also be passed at `__init__`
91
+ 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.
78
92
 
79
- vert = Vert(Coordinate(1, 2))
80
- assert vert.get_attrib(Coordinate).value == (1, 2)
93
+ These element attributes can also be passed at `__init__`
81
94
 
82
- 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).
95
+ ```python
96
+ vert = Vert(Coordinate(1, 2))
97
+ assert vert.get_attrib(Coordinate).value == (1, 2)
98
+ ```
83
99
 
84
100
  ### You Should Know
85
101
 
86
102
  A canonical half-edge data structure stores:
87
103
 
88
- * a set of verts (redundant)
89
- * for each vert, a pointer to an edge
90
- * a set of edges
91
- * for each edge, pointers to vert, pair, face, next
92
- * a set of faces (redundant)
93
- * for each face, a pointer to an edge
104
+ - a set of verts (redundant)
105
+ - for each vert, a pointer to an edge
106
+ - a set of edges
107
+ - for each edge, pointers to vert, pair, face, next
108
+ - a set of faces (redundant)
109
+ - for each face, a pointer to an edge
94
110
 
95
111
  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.
96
112
 
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "halfedge"
3
- version = "0.6.0"
3
+ version = "0.8.1"
4
4
  description = "A typical half-edge data structure with some padding"
5
5
  authors = [{ name = "Shay Hill", email = "shay_public@hotmail.com" }]
6
6
  license = {text = "MIT"}
7
7
  readme = "README.md"
8
- requires-python = ">=3.8"
8
+ requires-python = ">=3.9"
9
9
  dependencies = ["paragraphs"]
10
10
 
11
11
  [project.optional-dependencies]
@@ -25,7 +25,7 @@ build-backend = "setuptools.build_meta"
25
25
 
26
26
  [tool.commitizen]
27
27
  name = "cz_conventional_commits"
28
- version = "0.6.0"
28
+ version = "0.8.1"
29
29
  tag_format = "$version"
30
30
  major-version-zero = true
31
31
  version_files = ["pyproject.toml:^version"]
@@ -43,7 +43,7 @@ log_cli = 1
43
43
  [tool.tox]
44
44
  legacy_tox_ini = """
45
45
  [tox]
46
- envlist = py{38,39,310,311,312}
46
+ envlist = py{39,310,311,312,313}
47
47
 
48
48
  [testenv]
49
49
  deps = pytest
@@ -55,7 +55,7 @@ commands = pytest
55
55
  include = ["src"]
56
56
  exclude = ["**/__pycache__.py"]
57
57
 
58
- pythonVersion = "3.8"
58
+ pythonVersion = "3.9"
59
59
  pythonPlatform = "Any"
60
60
 
61
61
  typeCheckingMode = "strict"
@@ -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()
@@ -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__(
@@ -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,10 +1,10 @@
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
@@ -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
 
@@ -1,10 +1,11 @@
1
- """ simple HalfEdges instances for testing
1
+ """simple HalfEdges instances for testing
2
2
 
3
3
  created: 181121 13:14:06
4
4
  """
5
5
 
6
+ from collections.abc import Iterable, Sequence
6
7
  from itertools import product
7
- from typing import Any, Dict, Iterable, List, Sequence, Set, Tuple, TypeVar, cast
8
+ from typing import Any, TypeVar, cast
8
9
 
9
10
  import pytest
10
11
 
@@ -14,12 +15,12 @@ from halfedge.half_edge_object import HalfEdges
14
15
  from halfedge.type_attrib import IncompatibleAttrib
15
16
 
16
17
 
17
- class Coordinate(IncompatibleAttrib[Tuple[int, ...]]):
18
+ class Coordinate(IncompatibleAttrib[tuple[int, ...]]):
18
19
  pass
19
20
 
20
21
 
21
22
  @pytest.fixture
22
- def he_triangle() -> Dict[str, List[Any]]:
23
+ def he_triangle() -> dict[str, list[Any]]:
23
24
  """A simple triangle (inside and outside faces) for Mesh Element test"""
24
25
  mesh = HalfEdges()
25
26
  verts = [half_edge_elements.Vert(Coordinate(x)) for x in ((-1, 0), (1, 0), (0, 1))]
@@ -47,7 +48,7 @@ def he_triangle() -> Dict[str, List[Any]]:
47
48
 
48
49
 
49
50
  @pytest.fixture(scope="module")
50
- def meshes_vlvi() -> Dict[str, Any]:
51
+ def meshes_vlvi() -> dict[str, Any]:
51
52
  """A cube and a 3 x 3 grid"""
52
53
  # fmt: off
53
54
  cube_vl = [(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
@@ -73,13 +74,13 @@ def meshes_vlvi() -> Dict[str, Any]:
73
74
 
74
75
 
75
76
  @pytest.fixture(scope="function")
76
- def he_cube(meshes_vlvi: Dict[str, Any]) -> HalfEdges:
77
+ def he_cube(meshes_vlvi: dict[str, Any]) -> HalfEdges:
77
78
  vl = [Vert(Coordinate(x)) for x in meshes_vlvi["cube_vl"]]
78
79
  return HalfEdges.from_vlfi(vl, meshes_vlvi["cube_vi"])
79
80
 
80
81
 
81
82
  @pytest.fixture(scope="function")
82
- def he_grid(meshes_vlvi: Dict[str, Any]) -> HalfEdges:
83
+ def he_grid(meshes_vlvi: dict[str, Any]) -> HalfEdges:
83
84
  vl = [Vert(Coordinate(x)) for x in meshes_vlvi["grid_vl"]]
84
85
  return HalfEdges.from_vlfi(vl, meshes_vlvi["grid_vi"])
85
86
 
@@ -97,7 +98,7 @@ def he_mesh(
97
98
  @pytest.fixture(scope="function", params=range(9))
98
99
  def grid_faces(
99
100
  request: pytest.FixtureRequest, he_grid: HalfEdges
100
- ) -> Tuple[HalfEdges, Face]:
101
+ ) -> tuple[HalfEdges, Face]:
101
102
  """A cube and a 3 x 3 grid as HalfEdges instances"""
102
103
  idx = cast(int, request.param)
103
104
  return he_grid, sorted(he_grid.faces)[idx]
@@ -106,7 +107,7 @@ def grid_faces(
106
107
  @pytest.fixture(scope="function", params=range(6))
107
108
  def cube_faces(
108
109
  request: pytest.FixtureRequest, he_cube: HalfEdges
109
- ) -> Tuple[HalfEdges, Face]:
110
+ ) -> tuple[HalfEdges, Face]:
110
111
  """A cube and a 3 x 3 grid as HalfEdges instances"""
111
112
  idx = cast(int, request.param)
112
113
  return he_cube, sorted(he_cube.faces)[idx]
@@ -115,9 +116,9 @@ def cube_faces(
115
116
  @pytest.fixture(params=range(2))
116
117
  def mesh_faces(
117
118
  request: pytest.FixtureRequest,
118
- grid_faces: Tuple[HalfEdges, Face],
119
- cube_faces: Tuple[HalfEdges, Face],
120
- ) -> Tuple[HalfEdges, Face]:
119
+ grid_faces: tuple[HalfEdges, Face],
120
+ cube_faces: tuple[HalfEdges, Face],
121
+ ) -> tuple[HalfEdges, Face]:
121
122
  """A cube and a 3 x 3 grid as HalfEdges instances"""
122
123
  if request.param == 0:
123
124
  return grid_faces
@@ -127,7 +128,7 @@ def mesh_faces(
127
128
  @pytest.fixture(scope="function", params=range(48))
128
129
  def grid_edges(
129
130
  request: pytest.FixtureRequest, he_grid: HalfEdges
130
- ) -> Tuple[HalfEdges, Edge]:
131
+ ) -> tuple[HalfEdges, Edge]:
131
132
  """A cube and a 3 x 3 grid as HalfEdges instances"""
132
133
  idx = cast(int, request.param)
133
134
  return he_grid, sorted(he_grid.edges)[idx]
@@ -136,7 +137,7 @@ def grid_edges(
136
137
  @pytest.fixture(scope="function", params=range(24))
137
138
  def cube_edges(
138
139
  request: pytest.FixtureRequest, he_cube: HalfEdges
139
- ) -> Tuple[HalfEdges, Edge]:
140
+ ) -> tuple[HalfEdges, Edge]:
140
141
  """A cube and a 3 x 3 grid as HalfEdges instances"""
141
142
  idx = cast(int, request.param)
142
143
  return he_cube, sorted(he_cube.edges)[idx]
@@ -145,9 +146,9 @@ def cube_edges(
145
146
  @pytest.fixture(params=range(2))
146
147
  def mesh_edges(
147
148
  request: pytest.FixtureRequest,
148
- grid_edges: Tuple[HalfEdges, Edge],
149
- cube_edges: Tuple[HalfEdges, Edge],
150
- ) -> Tuple[HalfEdges, Edge]:
149
+ grid_edges: tuple[HalfEdges, Edge],
150
+ cube_edges: tuple[HalfEdges, Edge],
151
+ ) -> tuple[HalfEdges, Edge]:
151
152
  """A cube and a 3 x 3 grid as HalfEdges instances"""
152
153
  if request.param == 0:
153
154
  return grid_edges
@@ -191,7 +192,7 @@ def compare_circular_2(
191
192
  _TAxis = TypeVar("_TAxis")
192
193
 
193
194
 
194
- def get_canonical_index_tuple(vals: Iterable[int]) -> Tuple[int, ...]:
195
+ def get_canonical_index_tuple(vals: Iterable[int]) -> tuple[int, ...]:
195
196
  """Return a tuple with the lowest value first.
196
197
 
197
198
  This is useful for comparing circular sequences. It will only be canonical when
@@ -204,13 +205,13 @@ def get_canonical_index_tuple(vals: Iterable[int]) -> Tuple[int, ...]:
204
205
 
205
206
 
206
207
  def get_canonical_vr(
207
- vr: Set[Tuple[Tuple[_TAxis, ...], ...]]
208
- ) -> Set[Tuple[Tuple[_TAxis, ...], ...]]:
208
+ vr: set[tuple[tuple[_TAxis, ...], ...]],
209
+ ) -> set[tuple[tuple[_TAxis, ...], ...]]:
209
210
  """Rotate each tuple in a set to start with its min item.
210
211
 
211
212
  See docstring for canonical_mesh.
212
213
  """
213
- vr_aligned: Set[Tuple[Tuple[_TAxis, ...], ...]] = set()
214
+ vr_aligned: set[tuple[tuple[_TAxis, ...], ...]] = set()
214
215
  for face_verts in vr:
215
216
  min_item_idx = face_verts.index(min(face_verts))
216
217
  vr_aligned.add(face_verts[min_item_idx:] + face_verts[:min_item_idx])
@@ -218,8 +219,8 @@ def get_canonical_vr(
218
219
 
219
220
 
220
221
  def get_canonical_mesh(
221
- vl: Sequence[Tuple[_TAxis, ...]], vi: Iterable[Tuple[int, ...]]
222
- ) -> Set[Tuple[Tuple[_TAxis, ...], ...]]:
222
+ vl: Sequence[tuple[_TAxis, ...]], vi: Iterable[tuple[int, ...]]
223
+ ) -> set[tuple[tuple[_TAxis, ...], ...]]:
223
224
  """Return a canonical mesh representation.
224
225
 
225
226
  Methods in this library represent meshes as
@@ -264,7 +265,7 @@ def get_canonical_mesh(
264
265
  This function produces an unambiguous representation so that such methods can be
265
266
  tested.
266
267
  """
267
- vr: Set[Tuple[Tuple[_TAxis, ...], ...]] = set()
268
+ vr: set[tuple[tuple[_TAxis, ...], ...]] = set()
268
269
  for face_indices in vi:
269
270
  vr.add(tuple(vl[x] for x in face_indices))
270
271
  return get_canonical_vr(vr)
@@ -8,7 +8,7 @@ created: 170204 14:22:23
8
8
  from __future__ import annotations
9
9
 
10
10
  import random
11
- from typing import Any, Tuple, TypeVar
11
+ from typing import Any, TypeVar
12
12
 
13
13
  import pytest
14
14
 
@@ -333,11 +333,9 @@ def test_edge_lap_fails(he_triangle: dict[str, Any]) -> None:
333
333
  assert "infinite" in err.value.args[0]
334
334
 
335
335
 
336
- class Coordinate(IncompatibleAttrib[Tuple[int, int, int]]):
336
+ class Coordinate(IncompatibleAttrib[tuple[int, int, int]]):
337
337
  """A subclass of IncompatibleAttrib."""
338
338
 
339
- pass
340
-
341
339
 
342
340
  class TestInitVert:
343
341
  def setup_method(self) -> None:
@@ -5,7 +5,7 @@ created: 170204 14:22:23
5
5
 
6
6
  # pyright: reportPrivateUsage=false
7
7
 
8
- from typing import Any, Dict, Tuple
8
+ from typing import Any
9
9
 
10
10
  import pytest
11
11
 
@@ -21,7 +21,7 @@ from halfedge.type_attrib import IncompatibleAttrib, NumericAttrib
21
21
  from tests.conftest import get_canonical_mesh
22
22
 
23
23
 
24
- class Coordinate(IncompatibleAttrib[Tuple[float, ...]]):
24
+ class Coordinate(IncompatibleAttrib[tuple[float, ...]]):
25
25
  pass
26
26
 
27
27
 
@@ -29,7 +29,7 @@ class TestFromVlfi:
29
29
  def test_raise_on_non_inferable_holes(self) -> None:
30
30
  """Raises if holes cannot be inferred."""
31
31
  vl = [Vert() for _ in range(7)]
32
- fi: list[Tuple[int, ...]] = [(0, 2, 3, 1), (3, 5, 6, 4)]
32
+ fi: list[tuple[int, ...]] = [(0, 2, 3, 1), (3, 5, 6, 4)]
33
33
  with pytest.raises(ManifoldMeshError) as err:
34
34
  _ = HalfEdges.from_vlfi(vl, fi)
35
35
  assert "Ambiguous 'next'" in err.value.args[0]
@@ -54,7 +54,7 @@ class TestMeshElementBase:
54
54
  assert flag1_defined.get_attrib(Flag2).value == 4
55
55
 
56
56
 
57
- def test_edge_lap_succeeds(he_triangle: Dict[str, Any]) -> None:
57
+ def test_edge_lap_succeeds(he_triangle: dict[str, Any]) -> None:
58
58
  """Returns to self when (func(func(func(....func(self))))) == self."""
59
59
  for edge in he_triangle["edges"]:
60
60
  assert _function_lap(lambda x: x.next, edge) == [
@@ -64,7 +64,7 @@ def test_edge_lap_succeeds(he_triangle: Dict[str, Any]) -> None:
64
64
  ]
65
65
 
66
66
 
67
- def test_edge_lap_fails(he_triangle: Dict[str, Any]) -> None:
67
+ def test_edge_lap_fails(he_triangle: dict[str, Any]) -> None:
68
68
  """Fails when self intersects."""
69
69
  edges = he_triangle["edges"]
70
70
  with pytest.raises(ManifoldMeshError) as err:
@@ -75,55 +75,55 @@ def test_edge_lap_fails(he_triangle: Dict[str, Any]) -> None:
75
75
  class TestElementSubclasses:
76
76
  """Test all three _MeshElementBase children."""
77
77
 
78
- def test_edge_face_edges(self, he_triangle: Dict[str, Any]) -> None:
78
+ def test_edge_face_edges(self, he_triangle: dict[str, Any]) -> None:
79
79
  """Edge next around face."""
80
80
  for edge in he_triangle["edges"]:
81
81
  assert tuple(edge.face_edges) == (edge, edge.next, edge.next.next)
82
82
 
83
- def test_face_edges(self, he_triangle: Dict[str, Any]) -> None:
83
+ def test_face_edges(self, he_triangle: dict[str, Any]) -> None:
84
84
  """Finds all edges, starting at face.edge."""
85
85
  for face in he_triangle["faces"]:
86
86
  assert tuple(face.edges) == tuple(face.edge.face_edges)
87
87
 
88
- def test_edge_face_verts(self, he_triangle: Dict[str, Any]) -> None:
88
+ def test_edge_face_verts(self, he_triangle: dict[str, Any]) -> None:
89
89
  """Is equivalent to edge.pair.next around orig."""
90
90
  for edge in he_triangle["edges"]:
91
91
  assert tuple(edge.vert_edges) == (edge, edge.pair.next)
92
92
 
93
- def test_vert_edges(self, he_triangle: Dict[str, Any]) -> None:
93
+ def test_vert_edges(self, he_triangle: dict[str, Any]) -> None:
94
94
  """Is equivalent to vert_edges for vert.edge."""
95
95
  for vert in he_triangle["verts"]:
96
96
  assert tuple(vert.edges) == tuple(vert.edge.vert_edges)
97
97
 
98
- def test_vert_verts(self, he_triangle: Dict[str, Any]) -> None:
98
+ def test_vert_verts(self, he_triangle: dict[str, Any]) -> None:
99
99
  """Is equivalent to vert_edge.dest for vert.edge."""
100
100
  for vert in he_triangle["verts"]:
101
101
  assert vert.neighbors == [x.dest for x in vert.edge.vert_edges]
102
102
 
103
- def test_vert_valence(self, he_triangle: Dict[str, Any]) -> None:
103
+ def test_vert_valence(self, he_triangle: dict[str, Any]) -> None:
104
104
  """Valence is two for every corner in a triangle."""
105
105
  for vert in he_triangle["verts"]:
106
106
  assert vert.valence == 2
107
107
 
108
- def test_prev_by_face_edges(self, he_triangle: Dict[str, Any]) -> None:
108
+ def test_prev_by_face_edges(self, he_triangle: dict[str, Any]) -> None:
109
109
  """Previous edge will 'next' to self."""
110
110
  for edge in he_triangle["edges"]:
111
111
  assert edge.prev.next == edge
112
112
 
113
113
  @staticmethod
114
- def test_dest_is_next_orig(he_triangle: Dict[str, Any]) -> None:
114
+ def test_dest_is_next_orig(he_triangle: dict[str, Any]) -> None:
115
115
  """Finds orig of next or pair edge."""
116
116
  for edge in he_triangle["edges"]:
117
117
  assert edge.dest is edge.next.orig
118
118
 
119
119
  @staticmethod
120
- def test_face_verts(he_triangle: Dict[str, Any]) -> None:
120
+ def test_face_verts(he_triangle: dict[str, Any]) -> None:
121
121
  """Returns orig for every edge in face_verts."""
122
122
  for face in he_triangle["faces"]:
123
123
  assert tuple(face.verts) == tuple(face.edge.face_verts)
124
124
 
125
125
 
126
- def test_half_edges_init(he_triangle: Dict[str, Any]) -> None:
126
+ def test_half_edges_init(he_triangle: dict[str, Any]) -> None:
127
127
  """Verts, edges, faces, and holes match hand-calculated coordinates."""
128
128
  verts = set(he_triangle["verts"])
129
129
  edges = set(he_triangle["edges"])
@@ -142,7 +142,7 @@ class TestHalfEdges:
142
142
  """Keep the linter happy."""
143
143
 
144
144
  def test_vi(
145
- self, meshes_vlvi: Dict[str, Any], he_grid: HalfEdges, he_cube: HalfEdges
145
+ self, meshes_vlvi: dict[str, Any], he_grid: HalfEdges, he_cube: HalfEdges
146
146
  ) -> None:
147
147
  """Convert unaltered mesh faces back to input vi."""
148
148
  for mesh, key in ((he_grid, "grid"), (he_cube, "cube")):
@@ -153,7 +153,7 @@ class TestHalfEdges:
153
153
  )
154
154
  assert expect == result
155
155
 
156
- def test_hi(self, meshes_vlvi: Dict[str, Any], he_grid: HalfEdges) -> None:
156
+ def test_hi(self, meshes_vlvi: dict[str, Any], he_grid: HalfEdges) -> None:
157
157
  """Convert unaltered mesh holes back to input holes."""
158
158
  input_vl, input_hi = meshes_vlvi["grid_vl"], meshes_vlvi["grid_hi"]
159
159
  expect = get_canonical_mesh(input_vl, input_hi)
@@ -8,8 +8,6 @@ tested elsewhere.
8
8
  :created: 2024-08-09
9
9
  """
10
10
 
11
- from typing import Tuple
12
-
13
11
  import pytest
14
12
 
15
13
  from halfedge.half_edge_elements import Face, Vert
@@ -24,7 +22,7 @@ class TestVert:
24
22
  def test_holes(self) -> None:
25
23
  """Test that holes are correctly identified."""
26
24
  vl = [Vert() for _ in range(3)]
27
- fi: list[Tuple[int, ...]] = [(0, 1, 2)]
25
+ fi: list[tuple[int, ...]] = [(0, 1, 2)]
28
26
  mesh = HalfEdges.from_vlfi(vl, fi)
29
27
  assert len(mesh.holes) == 1
30
28
 
@@ -53,7 +51,7 @@ class TestEdge:
53
51
  def test_vert_faces(self, edge_index: int) -> None:
54
52
  """Test that vert_faces returns the correct faces."""
55
53
  vl = [Vert() for _ in range(3)]
56
- fi: list[Tuple[int, ...]] = [(0, 1, 2)]
54
+ fi: list[tuple[int, ...]] = [(0, 1, 2)]
57
55
  mesh = HalfEdges.from_vlfi(vl, fi)
58
56
  assert len(mesh.faces) == 1
59
57
  (face,) = mesh.faces
@@ -64,7 +62,7 @@ class TestEdge:
64
62
  def test_vert_holes(self, edge_index: int) -> None:
65
63
  """Test that vert_holes returns the correct holes."""
66
64
  vl = [Vert() for _ in range(3)]
67
- fi: list[Tuple[int, ...]] = [(0, 1, 2)]
65
+ fi: list[tuple[int, ...]] = [(0, 1, 2)]
68
66
  mesh = HalfEdges.from_vlfi(vl, fi)
69
67
  assert len(mesh.holes) == 1
70
68
  (hole,) = mesh.holes
@@ -1,4 +1,4 @@
1
- """ test halfedge.operations
1
+ """test halfedge.operations
2
2
 
3
3
  created: 181121 13:38:46
4
4
 
@@ -10,7 +10,7 @@ import random
10
10
  from contextlib import suppress
11
11
  from itertools import chain, combinations, permutations
12
12
  from operator import attrgetter
13
- from typing import Any, Tuple
13
+ from typing import Any
14
14
 
15
15
  import pytest
16
16
 
@@ -25,7 +25,7 @@ class NamedAttribute(IncompatibleAttrib[str]):
25
25
  """For color, flags. etc. to ensure attributes are passed"""
26
26
 
27
27
 
28
- class Coordinate(IncompatibleAttrib[Tuple[float, ...]]):
28
+ class Coordinate(IncompatibleAttrib[tuple[float, ...]]):
29
29
  """Hold coordinates when creating a mesh from a list of vertices"""
30
30
 
31
31
 
@@ -3,8 +3,6 @@
3
3
  created: 170204 14:22:23
4
4
  """
5
5
 
6
- from typing import Tuple
7
-
8
6
  import pytest
9
7
 
10
8
  from halfedge.half_edge_elements import ManifoldMeshError, Vert
@@ -14,7 +12,7 @@ from halfedge.type_attrib import IncompatibleAttrib
14
12
  from halfedge.validations import validate_mesh
15
13
 
16
14
 
17
- class Coordinate(IncompatibleAttrib[Tuple[float, ...]]):
15
+ class Coordinate(IncompatibleAttrib[tuple[float, ...]]):
18
16
  pass
19
17
 
20
18
 
File without changes
File without changes
File without changes
File without changes