halfedge 0.2.0__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.
@@ -0,0 +1,110 @@
1
+ # Run tests then upload to Pypi on version bumps.
2
+ # Run tests on each push.
3
+ # Try to bump version
4
+ # If version is bumped, upload to pypi or test.pypi depending on branch name.
5
+
6
+ name: pypi project
7
+
8
+ on:
9
+ push:
10
+ branches: [dev, main]
11
+ pull_request:
12
+ branches: [main]
13
+
14
+ jobs:
15
+ tests:
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
21
+ os: [ubuntu-latest, macos-latest, windows-latest]
22
+ # if: startsWith(github.event.head_commit.message, 'bump:') == false
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - name: Set up Python ${{ matrix.python-version }}
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ - name: Install dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
33
+ python -m pip install pytest
34
+ python -m pip install commitizen
35
+ python -m pip install .
36
+
37
+ - name: Test with pytest
38
+ run: |
39
+ pytest
40
+
41
+ # If the tests pass, try to bump the version number. If no bump is warranted,
42
+ # pass silently.
43
+ bump_version:
44
+ runs-on: ubuntu-latest
45
+ name: "Bump version and create changelog with commitizen"
46
+ continue-on-error: true
47
+ needs: [tests]
48
+ if: github.ref == 'refs/heads/dev'
49
+ steps:
50
+ - name: Check out
51
+ uses: actions/checkout@v4
52
+ with:
53
+ fetch-depth: 0
54
+ token: "${{ secrets.COMMITIZEN_BUMP }}"
55
+ - id: cz
56
+ name: Create bump and changelog
57
+ uses: commitizen-tools/commitizen-action@master
58
+ with:
59
+ github_token: ${{ secrets.COMMITIZEN_BUMP }}
60
+ - name: Print Version
61
+ run: echo "Bumped to version ${{ steps.cz.outputs.version }}"
62
+
63
+ # Deploy on test.pypi when branch is dev and commit message starts with 'bump'
64
+ deploy-on-testpypi:
65
+ runs-on: ubuntu-latest
66
+ continue-on-error: true
67
+ needs: [tests]
68
+ if: github.ref_name == 'dev' && startsWith(github.event.head_commit.message, 'bump:')
69
+ steps:
70
+ - uses: actions/checkout@v4
71
+ - name: Set up Python
72
+ uses: actions/setup-python@v5
73
+ with:
74
+ python-version: '3.x'
75
+ - name: Install dependencies
76
+ run: |
77
+ python -m pip install --upgrade pip
78
+ pip install build
79
+ - name: Build package
80
+ run: python -m build
81
+ - name: Publish package
82
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
83
+ with:
84
+ repository_url: https://test.pypi.org/legacy/
85
+ user: __token__
86
+ password: ${{ secrets.TEST_PYPI_API_TOKEN }}
87
+
88
+ # Deploy on pypi when branch is main and commit message starts with 'bump'
89
+ deploy-on-pypi:
90
+ runs-on: ubuntu-latest
91
+ continue-on-error: true
92
+ needs: [tests]
93
+ if: github.ref_name == 'main' && startsWith(github.event.head_commit.message, 'bump:')
94
+ steps:
95
+ - uses: actions/checkout@v4
96
+ - name: Set up Python
97
+ uses: actions/setup-python@v5
98
+ with:
99
+ python-version: '3.x'
100
+ - name: Install dependencies
101
+ run: |
102
+ python -m pip install --upgrade pip
103
+ pip install build
104
+ - name: Build package
105
+ run: python -m build
106
+ - name: Publish package
107
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
108
+ with:
109
+ user: __token__
110
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1 @@
1
+ Update-PythonVenv.ps1
@@ -0,0 +1,145 @@
1
+ ci:
2
+ skip: [pyright]
3
+
4
+ repos:
5
+
6
+ - repo: https://github.com/pre-commit/pre-commit-hooks
7
+ rev: v4.6.0
8
+ hooks:
9
+ - id: check-added-large-files
10
+ - id: check-ast
11
+ - id: check-case-conflict
12
+ - id: check-docstring-first
13
+ - id: check-executables-have-shebangs
14
+ - id: check-json
15
+ - id: check-merge-conflict
16
+ args:
17
+ - --assume-in-merge
18
+ - id: check-shebang-scripts-are-executable
19
+ - id: check-symlinks
20
+ - id: check-toml
21
+ - id: check-vcs-permalinks
22
+ - id: check-xml
23
+ - id: check-yaml
24
+ - id: debug-statements
25
+ - id: destroyed-symlinks
26
+ # - id: detect-aws-credentials
27
+ - id: detect-private-key
28
+ - id: end-of-file-fixer
29
+ - id: mixed-line-ending
30
+ - id: requirements-txt-fixer
31
+ - id: trailing-whitespace
32
+ - id: fix-encoding-pragma
33
+ args:
34
+ - --remove
35
+ - id: name-tests-test
36
+ args:
37
+ - --pytest-test-first
38
+ - id: no-commit-to-branch
39
+ - id: pretty-format-json
40
+ args: ['--autofix']
41
+ # - id: sort-simple-yaml
42
+ # files: .pre-commit-config.yaml
43
+
44
+ - repo: https://github.com/pre-commit/mirrors-mypy
45
+ rev: v1.11.1
46
+ hooks:
47
+ - id: mypy
48
+ name: mypy
49
+ language: python
50
+ language_version: python 3.12
51
+ types: [python]
52
+ require_serial: true
53
+ verbose: true
54
+ additional_dependencies: ['types-requests']
55
+ # exclude: "tests"
56
+ # args:
57
+ # - --ignore-missing-imports
58
+ # files: ^(src/|tests/)
59
+
60
+ - repo: https://github.com/PyCQA/isort
61
+ rev: 5.13.2
62
+ hooks:
63
+ - id: isort
64
+ args: ["--profile", "black", "--filter-files", "--combine-as", "honor--noqa"]
65
+
66
+ - repo: https://github.com/psf/black
67
+ rev: 24.8.0
68
+ hooks:
69
+ - id: black
70
+ language_version: python3.8
71
+ args: ["--skip-magic-trailing-comma"]
72
+
73
+ - repo: https://github.com/asottile/pyupgrade
74
+ rev: v3.17.0
75
+ hooks:
76
+ - args:
77
+ - --py38-plus
78
+ id: pyupgrade
79
+
80
+ - repo: https://github.com/Lucas-C/pre-commit-hooks
81
+ rev: v1.5.5
82
+ hooks:
83
+ - id: remove-tabs
84
+
85
+ - repo: https://github.com/commitizen-tools/commitizen
86
+ rev: v3.28.0
87
+ hooks:
88
+ - id: commitizen
89
+
90
+ - repo: local
91
+ hooks:
92
+ - id: pylint
93
+ exclude: "tests"
94
+ name: pylint
95
+ entry: ./venv/Scripts/pylint.exe
96
+ language: system
97
+ types: [python]
98
+ args:
99
+ - --ignore-patterns=venv
100
+ - --good-names=i,j,_,f
101
+ # - --disable=protected-access
102
+ # - --disable=no-member
103
+ # - --disable=import-error
104
+ # - --disable=no-name-in-module
105
+ - --disable=redundant-returns-doc # many false positives
106
+ - --disable=too-many-arguments
107
+ - --disable=too-many-instance-attributes
108
+ - --disable=redefined-builtin # sticking with edge.next
109
+ - --disable=invalid-name # for _TMeshElem and similar type hints
110
+ - --disable=useless-return # conflicts with mypy
111
+ - --disable=assignment-from-no-return # prevents overloading Attrib methods to only raise an Exception
112
+ - --disable=useless-parent-delegation
113
+ - --load-plugins=pylint.extensions.docparams
114
+ - --accept-no-param-doc=n
115
+ - --accept-no-raise-doc=n
116
+ - --accept-no-return-doc=n
117
+ - --accept-no-yields-doc=n
118
+
119
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
120
+ # ignores
121
+ # COM812 Trailing comma missing (does not agree with Black)
122
+ # D203 1 blank line required before class docstring (incompatible with D211)
123
+ # D213 multi line summary second line (incompatible with D212):
124
+ # ISC003 Explicitly concatenated string should be implicitly concatenated
125
+ #
126
+ # PYI019 Use typing.Self instead of custom TypeVar (useless py < 3.10)
127
+ # A002 shadowing built-in 'next'
128
+ # PLR2004 magic value used in comparison
129
+ # D403 First word of the first line should be properly capitalized
130
+ # PLR0913 too namy arguments
131
+ rev: 'v0.5.7'
132
+ hooks:
133
+ - id: ruff
134
+ exclude: "tests"
135
+ args:
136
+ - --target-version=py38
137
+ - --select=ALL
138
+ - --ignore=COM812,D203,D213,ISC003,PYI019,A002,PLR2004,D403,PLR0913
139
+ # - --fix
140
+
141
+ # reads pyproject.toml for additional config
142
+ - repo: https://github.com/RobertCraigie/pyright-python
143
+ rev: v1.1.375
144
+ hooks:
145
+ - id: pyright
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.1
2
+ Name: halfedge
3
+ Version: 0.2.0
4
+ Summary: A typical half-edge data structure with some padding
5
+ Author-email: Shay Hill <shay_public@hotmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: paragraphs
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"
17
+
18
+ # A typical halfedges data structure with some padding
19
+
20
+ ## Use
21
+
22
+ You will most likely want to create meshes with the `from_vlfi` constructor. It's got a few tricks, so be sure to read the docstring.
23
+
24
+ ```python
25
+ from halfedges import HalfEdges, Vert
26
+
27
+ vertices = [Vert() for _ in range(4)]
28
+ face_indices: list[tuple[int, ...]] = [(0, 1, 2), (0, 2, 3)]
29
+
30
+ mesh = HalfEdges.from_vlfi(vertices, face_indices)
31
+
32
+ edge = next(iter(mesh.edges))
33
+ mesh.remove_edge(edge)
34
+ ```
35
+
36
+ ## Particulars
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.
39
+
40
+ ### Reflection
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`.
43
+
44
+ ### id
45
+
46
+ Each Vert, Edge, and Face instance has an `id` attribute. This is a unique, sequential identifier for the instance. Sorted vert, edge, and face lists (`HalfEdges.vl`, `HalfEdges.el`, `HalfEdges.fl`) are sorted by `id`. All Vert, Edge, and Face instances across all HalfEdges instances will share the id counter, so don't expect the id of your verts to start at 0.
47
+
48
+ ### Holes
49
+
50
+ A triangle mesh in 2D will never be entirely triangular (and also manifold). There will be a boundary around the triangles. This library treats the outside of that boundary as a face (with an `is_hole == True` attribute). This way, every edge has a pair and a face, and the `is_hole` flags can be used to keep these hole faces out of your way.
51
+
52
+ Holes are useful for more than just 2D mesh boundaries, you can explicitly create holes (`Face` instances with an as `is_hole == True` property) to maintain manifold mesh conditions in many circumstances. The constructor `HalfEdges.from_vlfi()` will try to insert hole faces to maintain manifold conditions.
53
+
54
+ Four main types: Vert, Edge, Face, and HalfEdges. Vert and Face instances have `*.faces` properties which will return all adjacent faces. These properties will *not* return faces flagged as holes. The holes are there to keep things simple and manifold, but otherwise stay out of your way.
55
+
56
+ ```python
57
+ Face(
58
+ *attributes: Attrib[Any],
59
+ mesh: BlindHalfEdges | None = None,
60
+ edge: Edge | None = None,
61
+ is_hole: bool = False,
62
+ ) -> None:
63
+ ```
64
+
65
+ The `is_hole` `__init__` kwarg is shorthand for
66
+
67
+ class IsHole(ContagionAttribute):
68
+ pass
69
+
70
+ vert = Vert()
71
+ vert.add_attrib(IsHole())
72
+
73
+ The `face_instance.is_hole` property getter is shorthand for
74
+
75
+ vert.get_attrib(IsHole())
76
+
77
+ More on `Attrib` classes below and in `type_attrib.py`.
78
+
79
+ ### Element Attributes
80
+
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?
82
+
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.
84
+
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.
86
+
87
+ class Coordinate(IncompatibleAttribute[Tuple[float, float]]):
88
+ pass
89
+
90
+ vert = Vert()
91
+ vert.add_attrib(Coordinate((1, 2)))
92
+ assert vert.get_attrib(Coordinate).value == (1, 2)
93
+
94
+ These element attributes can also be passed at `__init__`
95
+
96
+ vert = Vert(Coordinate(1, 2))
97
+ assert vert.get_attrib(Coordinate).value == (1, 2)
98
+
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).
100
+
101
+ ### You Should Know
102
+
103
+ A canonical half-edge data structure stores:
104
+
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
111
+
112
+ 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
+
114
+ This means that verts and faces disappear from a mesh when the edges referencing them are removed.
115
+
116
+ Also means that this won't be useful for more than hundreds (maybe thousands) of edges. Good enough for my purposes.
117
+
118
+ ## Project Structure
119
+
120
+ The HalfEdges class is defined across three modules:
121
+
122
+ ### half_edge_constructors.py
123
+
124
+ class BlindHalfEdges defines the from_vlfi constructor and just enough methods to allow it to work. These methods define how pointers and Attrib instances are assigned.
125
+
126
+ ### half_edge_querries.py
127
+
128
+ class StaticHalfEdges(BlindHalfEdges) defines all the look-up tricks of the halfedge data structure. What faces are adjacent to an edge? What edges radiate from a vert? etc.
129
+
130
+ ### half_edge_object.py
131
+
132
+ class HalfEdges(StaticHalfEdges) defines all of the methods that change the structure of the mesh: insert_edge, collapse_edge, etc.
@@ -0,0 +1,115 @@
1
+ # A typical halfedges data structure with some padding
2
+
3
+ ## Use
4
+
5
+ You will most likely want to create meshes with the `from_vlfi` constructor. It's got a few tricks, so be sure to read the docstring.
6
+
7
+ ```python
8
+ from halfedges import HalfEdges, Vert
9
+
10
+ vertices = [Vert() for _ in range(4)]
11
+ face_indices: list[tuple[int, ...]] = [(0, 1, 2), (0, 2, 3)]
12
+
13
+ mesh = HalfEdges.from_vlfi(vertices, face_indices)
14
+
15
+ edge = next(iter(mesh.edges))
16
+ mesh.remove_edge(edge)
17
+ ```
18
+
19
+ ## Particulars
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.
22
+
23
+ ### Reflection
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`.
26
+
27
+ ### id
28
+
29
+ Each Vert, Edge, and Face instance has an `id` attribute. This is a unique, sequential identifier for the instance. Sorted vert, edge, and face lists (`HalfEdges.vl`, `HalfEdges.el`, `HalfEdges.fl`) are sorted by `id`. All Vert, Edge, and Face instances across all HalfEdges instances will share the id counter, so don't expect the id of your verts to start at 0.
30
+
31
+ ### Holes
32
+
33
+ A triangle mesh in 2D will never be entirely triangular (and also manifold). There will be a boundary around the triangles. This library treats the outside of that boundary as a face (with an `is_hole == True` attribute). This way, every edge has a pair and a face, and the `is_hole` flags can be used to keep these hole faces out of your way.
34
+
35
+ Holes are useful for more than just 2D mesh boundaries, you can explicitly create holes (`Face` instances with an as `is_hole == True` property) to maintain manifold mesh conditions in many circumstances. The constructor `HalfEdges.from_vlfi()` will try to insert hole faces to maintain manifold conditions.
36
+
37
+ Four main types: Vert, Edge, Face, and HalfEdges. Vert and Face instances have `*.faces` properties which will return all adjacent faces. These properties will *not* return faces flagged as holes. The holes are there to keep things simple and manifold, but otherwise stay out of your way.
38
+
39
+ ```python
40
+ Face(
41
+ *attributes: Attrib[Any],
42
+ mesh: BlindHalfEdges | None = None,
43
+ edge: Edge | None = None,
44
+ is_hole: bool = False,
45
+ ) -> None:
46
+ ```
47
+
48
+ The `is_hole` `__init__` kwarg is shorthand for
49
+
50
+ class IsHole(ContagionAttribute):
51
+ pass
52
+
53
+ vert = Vert()
54
+ vert.add_attrib(IsHole())
55
+
56
+ The `face_instance.is_hole` property getter is shorthand for
57
+
58
+ vert.get_attrib(IsHole())
59
+
60
+ More on `Attrib` classes below and in `type_attrib.py`.
61
+
62
+ ### Element Attributes
63
+
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?
65
+
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.
67
+
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.
69
+
70
+ class Coordinate(IncompatibleAttribute[Tuple[float, float]]):
71
+ pass
72
+
73
+ vert = Vert()
74
+ vert.add_attrib(Coordinate((1, 2)))
75
+ assert vert.get_attrib(Coordinate).value == (1, 2)
76
+
77
+ These element attributes can also be passed at `__init__`
78
+
79
+ vert = Vert(Coordinate(1, 2))
80
+ assert vert.get_attrib(Coordinate).value == (1, 2)
81
+
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).
83
+
84
+ ### You Should Know
85
+
86
+ A canonical half-edge data structure stores:
87
+
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
94
+
95
+ 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
+
97
+ This means that verts and faces disappear from a mesh when the edges referencing them are removed.
98
+
99
+ Also means that this won't be useful for more than hundreds (maybe thousands) of edges. Good enough for my purposes.
100
+
101
+ ## Project Structure
102
+
103
+ The HalfEdges class is defined across three modules:
104
+
105
+ ### half_edge_constructors.py
106
+
107
+ class BlindHalfEdges defines the from_vlfi constructor and just enough methods to allow it to work. These methods define how pointers and Attrib instances are assigned.
108
+
109
+ ### half_edge_querries.py
110
+
111
+ class StaticHalfEdges(BlindHalfEdges) defines all the look-up tricks of the halfedge data structure. What faces are adjacent to an edge? What edges radiate from a vert? etc.
112
+
113
+ ### half_edge_object.py
114
+
115
+ class HalfEdges(StaticHalfEdges) defines all of the methods that change the structure of the mesh: insert_edge, collapse_edge, etc.
@@ -0,0 +1,72 @@
1
+ [project]
2
+ name = "halfedge"
3
+ version = "0.2.0"
4
+ description = "A typical half-edge data structure with some padding"
5
+ authors = [{ name = "Shay Hill", email = "shay_public@hotmail.com" }]
6
+ license = {text = "MIT"}
7
+ readme = "README.md"
8
+ requires-python = ">=3.8"
9
+ dependencies = ["paragraphs"]
10
+
11
+ [project.optional-dependencies]
12
+ dev = [
13
+ "commitizen",
14
+ "coverage",
15
+ "pre-commit",
16
+ "pylint",
17
+ "pytest",
18
+ "tox"
19
+ ]
20
+
21
+ [build-system]
22
+ requires = ["setuptools", "setuptools-scm"]
23
+ build-backend = "setuptools.build_meta"
24
+
25
+
26
+ [tool.commitizen]
27
+ name = "cz_conventional_commits"
28
+ version = "0.2.0"
29
+ tag_format = "$version"
30
+ major-version-zero = true
31
+ version_files = ["pyproject.toml:^version"]
32
+
33
+
34
+ [tool.isort]
35
+ profile = "black"
36
+
37
+
38
+ [tool.pytest.ini_options]
39
+ # addopts = "--doctest-modules"
40
+ pythonpath = ["tests"]
41
+ log_cli = 1
42
+
43
+ [tool.tox]
44
+ legacy_tox_ini = """
45
+ [tox]
46
+ envlist = py{38,39,310,311,312}
47
+
48
+ [testenv]
49
+ deps = pytest
50
+ commands = pytest
51
+ """
52
+
53
+
54
+ [tool.pyright]
55
+ include = ["src"]
56
+ exclude = ["**/__pycache__.py"]
57
+
58
+ pythonVersion = "3.8"
59
+ pythonPlatform = "Any"
60
+
61
+ typeCheckingMode = "strict"
62
+ reportShadowedImports = true
63
+ reportCallInDefaultInitializer = true
64
+ reportImplicitStringConcatenation = true
65
+ # reportMissingSuperCall = true
66
+ reportPropertyTypeMismatch = true
67
+ reportUninitializedInstanceVariable = true
68
+ reportUnnecessaryTypeIgnoreComment = true
69
+ reportUnusedCallResult = true
70
+
71
+ venvPath = "."
72
+ venv = "./venv"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ """Allow modules to be imported from top-level."""
2
+
3
+ from halfedge.half_edge_elements import Edge, Face, Vert
4
+ from halfedge.half_edge_object import HalfEdges
5
+ from halfedge.type_attrib import (
6
+ Attrib,
7
+ ContagionAttrib,
8
+ IncompatibleAttrib,
9
+ NumericAttrib,
10
+ Vector2Attrib,
11
+ Vector3Attrib,
12
+ )
13
+
14
+ __all__ = [
15
+ "Attrib",
16
+ "ContagionAttrib",
17
+ "Edge",
18
+ "Face",
19
+ "HalfEdges",
20
+ "IncompatibleAttrib",
21
+ "NumericAttrib",
22
+ "Vector2Attrib",
23
+ "Vector3Attrib",
24
+ "Vert",
25
+ ]