stellarmesh 0.0.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.
- stellarmesh-0.0.0/LICENSE +21 -0
- stellarmesh-0.0.0/PKG-INFO +20 -0
- stellarmesh-0.0.0/README.rst +98 -0
- stellarmesh-0.0.0/pyproject.toml +61 -0
- stellarmesh-0.0.0/setup.cfg +4 -0
- stellarmesh-0.0.0/src/stellarmesh/__init__.py +34 -0
- stellarmesh-0.0.0/src/stellarmesh/_core.py +4 -0
- stellarmesh-0.0.0/src/stellarmesh/geometry.py +320 -0
- stellarmesh-0.0.0/src/stellarmesh/mesh.py +646 -0
- stellarmesh-0.0.0/src/stellarmesh/moab.py +708 -0
- stellarmesh-0.0.0/src/stellarmesh.egg-info/PKG-INFO +20 -0
- stellarmesh-0.0.0/src/stellarmesh.egg-info/SOURCES.txt +19 -0
- stellarmesh-0.0.0/src/stellarmesh.egg-info/dependency_links.txt +1 -0
- stellarmesh-0.0.0/src/stellarmesh.egg-info/requires.txt +12 -0
- stellarmesh-0.0.0/src/stellarmesh.egg-info/top_level.txt +1 -0
- stellarmesh-0.0.0/tests/test_examples.py +18 -0
- stellarmesh-0.0.0/tests/test_geometry.py +166 -0
- stellarmesh-0.0.0/tests/test_mesh.py +238 -0
- stellarmesh-0.0.0/tests/test_mesh_benchmark.py +33 -0
- stellarmesh-0.0.0/tests/test_moab.py +120 -0
- stellarmesh-0.0.0/tests/test_tally.py +418 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stellarmesh Developers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stellarmesh
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Meshing library for nuclear workflows.
|
|
5
|
+
Author: Alex Koen
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: pymmg
|
|
11
|
+
Requires-Dist: meshio[all]
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: cadquery-ocp-stubs==7.8.1.1.post1; extra == "dev"
|
|
14
|
+
Requires-Dist: ocp-vscode>=2.9.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pip>=25.2; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest>=8.4.1; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-benchmark>=5.1.0; extra == "dev"
|
|
19
|
+
Requires-Dist: ruff>=0.12.8; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.. figure:: https://github.com/Thea-Energy/stellarmesh/raw/main/docs/logo.png
|
|
2
|
+
:width: 100%
|
|
3
|
+
:align: center
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|Tests| |PyPI Version| |Conda Version|
|
|
7
|
+
|
|
8
|
+
Stellarmesh is a meshing library for nuclear workflows. Principally, it
|
|
9
|
+
supports the creation of DAGMC geometry from CAD models.
|
|
10
|
+
|
|
11
|
+
**Features**:
|
|
12
|
+
|
|
13
|
+
* ✅ Import of `CadQuery <https://github.com/CadQuery/cadquery>`__,
|
|
14
|
+
`build123d <https://github.com/gumyr/build123d>`__, STEP and BREP
|
|
15
|
+
geometry
|
|
16
|
+
* ✅ Surface and volume meshing
|
|
17
|
+
* ✅ Gmsh and OpenCASCADE meshing backends
|
|
18
|
+
* ✅ Linear and angular mesh tolerances
|
|
19
|
+
* ✅ Surface boundary conditions
|
|
20
|
+
* ✅ Imprinting and merging of conformal geometry
|
|
21
|
+
* ✅ Mesh refinement
|
|
22
|
+
* ✅ Programmatic manipulation of .h5m tags
|
|
23
|
+
* ✅ Automated testing and integration
|
|
24
|
+
|
|
25
|
+
---------------
|
|
26
|
+
Getting Started
|
|
27
|
+
---------------
|
|
28
|
+
|
|
29
|
+
* `Installation <https://stellarmesh.readthedocs.io/en/latest/install.html>`__
|
|
30
|
+
* `Tutorials <https://stellarmesh.readthedocs.io/en/latest/tutorials.html>`__
|
|
31
|
+
* `API <https://stellarmesh.readthedocs.io/en/latest/api>`__
|
|
32
|
+
|
|
33
|
+
-------
|
|
34
|
+
Example
|
|
35
|
+
-------
|
|
36
|
+
|
|
37
|
+
.. code:: python
|
|
38
|
+
|
|
39
|
+
import build123d as bd
|
|
40
|
+
import stellarmesh as sm
|
|
41
|
+
|
|
42
|
+
solids = [bd.Solid.make_torus(1000, 100)]
|
|
43
|
+
for _ in range(3):
|
|
44
|
+
solids.append(bd.Solid.thicken(solids[-1].faces()[0], 100))
|
|
45
|
+
solids = solids[1:]
|
|
46
|
+
|
|
47
|
+
geometry = sm.Geometry(solids[::-1], material_names=["a", "a", "c"])
|
|
48
|
+
mesh = sm.SurfaceMesh.from_geometry(
|
|
49
|
+
geometry, sm.GmshSurfaceOptions(min_mesh_size=50, max_mesh_size=200)
|
|
50
|
+
)
|
|
51
|
+
mesh.write("test.msh")
|
|
52
|
+
mesh.render("docs/torus-mesh-reversed.png", rotation_xyz=(90, 0, -90), normals=15)
|
|
53
|
+
|
|
54
|
+
h5m = sm.DAGMCModel.from_mesh(mesh)
|
|
55
|
+
h5m.write("dagmc.h5m")
|
|
56
|
+
h5m.write("dagmc.vtk")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
.. figure:: https://github.com/Thea-Energy/stellarmesh/raw/main/docs/torus-mesh.png
|
|
60
|
+
:width: 80%
|
|
61
|
+
:align: center
|
|
62
|
+
:alt: Rendered mesh with normals.
|
|
63
|
+
|
|
64
|
+
Rendered mesh with normals.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
.. note::
|
|
68
|
+
Stellarmesh uses the logging library for debug, info and warning messages. Set the level with:
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
.. code:: python
|
|
72
|
+
|
|
73
|
+
import logging
|
|
74
|
+
|
|
75
|
+
logging.basicConfig() # Required in Jupyter to correctly set output stream
|
|
76
|
+
logging.getLogger("stellarmesh").setLevel(logging.INFO)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
----------------
|
|
81
|
+
Acknowledgements
|
|
82
|
+
----------------
|
|
83
|
+
|
|
84
|
+
Stellarmesh is originally a project of Thea Energy, who are building the
|
|
85
|
+
world’s first planar coil stellarator.
|
|
86
|
+
|
|
87
|
+
.. raw:: html
|
|
88
|
+
|
|
89
|
+
<img src="https://github.com/user-attachments/assets/37b9ba1c-b22c-4837-b226-a6212854127e" width="200px">
|
|
90
|
+
|
|
91
|
+
.. raw:: html
|
|
92
|
+
|
|
93
|
+
.. |Tests| image:: https://github.com/stellarmesh/stellarmesh/actions/workflows/test.yml/badge.svg
|
|
94
|
+
:target: https://github.com/stellarmesh/stellarmesh/actions/workflows/test.yml
|
|
95
|
+
.. |PyPI Version| image:: https://img.shields.io/pypi/v/stellarmesh.svg
|
|
96
|
+
:target: https://pypi.org/project/stellarmesh/
|
|
97
|
+
.. |Conda Version| image:: https://img.shields.io/conda/vn/conda-forge/stellarmesh.svg
|
|
98
|
+
:target: https://anaconda.org/conda-forge/stellarmesh
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stellarmesh"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
authors = [{ name = "Alex Koen" }]
|
|
5
|
+
description = "Meshing library for nuclear workflows."
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
dependencies = ["numpy", "pymmg", "meshio[all]"]
|
|
9
|
+
|
|
10
|
+
[project.optional-dependencies]
|
|
11
|
+
dev = [
|
|
12
|
+
"cadquery-ocp-stubs==7.8.1.1.post1",
|
|
13
|
+
"ocp-vscode>=2.9.0",
|
|
14
|
+
"pip>=25.2",
|
|
15
|
+
"pytest>=8.4.1",
|
|
16
|
+
"pytest-cov>=6.2.1",
|
|
17
|
+
"pytest-benchmark>=5.1.0",
|
|
18
|
+
"ruff>=0.12.8",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["setuptools"]
|
|
23
|
+
build-backend = "setuptools.build_meta"
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
target-version = "py311"
|
|
27
|
+
extend-include = ["*.ipynb"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select = [
|
|
32
|
+
"E",
|
|
33
|
+
"W",
|
|
34
|
+
"I",
|
|
35
|
+
"N",
|
|
36
|
+
"A",
|
|
37
|
+
"B",
|
|
38
|
+
"F",
|
|
39
|
+
"D",
|
|
40
|
+
"FBT",
|
|
41
|
+
"C4",
|
|
42
|
+
"SIM",
|
|
43
|
+
"TD",
|
|
44
|
+
"PL",
|
|
45
|
+
"TRY",
|
|
46
|
+
"NPY",
|
|
47
|
+
"PERF",
|
|
48
|
+
"RUF",
|
|
49
|
+
]
|
|
50
|
+
ignore = [
|
|
51
|
+
"TRY003", # Exception with string (vs. defining custom exception)
|
|
52
|
+
"PLR2004", # Magic number
|
|
53
|
+
]
|
|
54
|
+
pydocstyle.convention = "google"
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
57
|
+
"tests/**/test_*.py" = ["F811"] # Variable redefinition (required for fixtures)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
[tool.pyright]
|
|
61
|
+
pythonVersion = "3.11"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Gmsh wrapper and DAGMC geometry creator."""
|
|
2
|
+
|
|
3
|
+
from .geometry import Geometry
|
|
4
|
+
from .mesh import (
|
|
5
|
+
GmshSurfaceAlgo,
|
|
6
|
+
GmshSurfaceOptions,
|
|
7
|
+
GmshVolumeAlgo,
|
|
8
|
+
GmshVolumeOptions,
|
|
9
|
+
Mesh,
|
|
10
|
+
OCCSurfaceAlgo,
|
|
11
|
+
OCCSurfaceOptions,
|
|
12
|
+
SurfaceMesh,
|
|
13
|
+
VolumeMesh,
|
|
14
|
+
)
|
|
15
|
+
from .moab import DAGMCModel, DAGMCSurface, DAGMCVolume, MOABModel
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DAGMCModel",
|
|
19
|
+
"DAGMCSurface",
|
|
20
|
+
"DAGMCVolume",
|
|
21
|
+
"Geometry",
|
|
22
|
+
"GmshSurfaceAlgo",
|
|
23
|
+
"GmshSurfaceOptions",
|
|
24
|
+
"GmshVolumeAlgo",
|
|
25
|
+
"GmshVolumeOptions",
|
|
26
|
+
"MOABModel",
|
|
27
|
+
"Mesh",
|
|
28
|
+
"OCCSurfaceAlgo",
|
|
29
|
+
"OCCSurfaceOptions",
|
|
30
|
+
"SurfaceMesh",
|
|
31
|
+
"VolumeMesh",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Stellarmesh geometry.
|
|
2
|
+
|
|
3
|
+
name: geometry.py
|
|
4
|
+
author: Alex Koen
|
|
5
|
+
|
|
6
|
+
desc: Geometry class represents a CAD geometry to be meshed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import warnings
|
|
13
|
+
from typing import (
|
|
14
|
+
Optional,
|
|
15
|
+
Protocol,
|
|
16
|
+
Sequence,
|
|
17
|
+
Type,
|
|
18
|
+
overload,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from OCP.BOPAlgo import BOPAlgo_MakeConnected
|
|
23
|
+
from OCP.BRep import BRep_Builder
|
|
24
|
+
from OCP.BRepTools import BRepTools
|
|
25
|
+
from OCP.IFSelect import IFSelect_RetDone
|
|
26
|
+
from OCP.STEPControl import STEPControl_Reader
|
|
27
|
+
from OCP.TopAbs import TopAbs_ShapeEnum
|
|
28
|
+
from OCP.TopExp import TopExp_Explorer
|
|
29
|
+
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
|
|
30
|
+
except ImportError as e:
|
|
31
|
+
raise ImportError(
|
|
32
|
+
"OCP not found. See Stellarmesh installation instructions."
|
|
33
|
+
) from e
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Face(Protocol):
|
|
39
|
+
"""Interface for a CadQuery or Build123d Face."""
|
|
40
|
+
|
|
41
|
+
wrapped: TopoDS_Face | None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Shell(Protocol):
|
|
45
|
+
"""Interface for a CadQuery or Build123d Shell."""
|
|
46
|
+
|
|
47
|
+
wrapped: TopoDS_Shell | None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Solid(Protocol):
|
|
51
|
+
"""Interface for a CadQuery or Build123d Solid."""
|
|
52
|
+
|
|
53
|
+
wrapped: TopoDS_Solid | None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Geometry:
|
|
57
|
+
"""Geometry, representing an ordered list of solids, to be meshed."""
|
|
58
|
+
|
|
59
|
+
solids: list[TopoDS_Solid]
|
|
60
|
+
material_names: list[str]
|
|
61
|
+
faces: list[TopoDS_Face]
|
|
62
|
+
face_boundary_conditions: list[str]
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
solids: Optional[Sequence[Solid | TopoDS_Solid]] = None,
|
|
67
|
+
material_names: Optional[Sequence[str]] = None,
|
|
68
|
+
surfaces: Optional[Sequence[Face | Shell | TopoDS_Face | TopoDS_Shell]] = None,
|
|
69
|
+
surface_boundary_conditions: Optional[Sequence[str]] = None,
|
|
70
|
+
):
|
|
71
|
+
"""Construct geometry from solids.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
solids: List of solids, where each solid is a build123d Solid, CadQuery
|
|
75
|
+
Solid, or OCP TopoDS_Solid.
|
|
76
|
+
material_names: List of materials. Must match length of solids.
|
|
77
|
+
surfaces: List of surfaces, where each surface is a build123d or Cadquery
|
|
78
|
+
Face or Shell, or an OCP TopoDS_Face or TopoDS_Shell.
|
|
79
|
+
surface_boundary_conditions: List of boundary condition names. Must match
|
|
80
|
+
length of surfaces.
|
|
81
|
+
"""
|
|
82
|
+
if (solids and not material_names) or (material_names and not solids):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"If solids or material_names are provided"
|
|
85
|
+
", both must be provided and match in length."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if (surfaces and not surface_boundary_conditions) or (
|
|
89
|
+
surface_boundary_conditions and not surfaces
|
|
90
|
+
):
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"If surfaces or surface_boundary_conditions are provided"
|
|
93
|
+
", both must be provided and match in length."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
self.solids = []
|
|
97
|
+
self.material_names = []
|
|
98
|
+
if solids and material_names:
|
|
99
|
+
for i, (s, mat_name) in enumerate(zip(solids, material_names, strict=True)):
|
|
100
|
+
s_wrapped = (
|
|
101
|
+
s if isinstance(s, TopoDS_Shape) else getattr(s, "wrapped", None)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if s_wrapped is None:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"{s} {i} has no wrapped TopoDS_Shape. Is it valid?"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.solids.append(s_wrapped)
|
|
110
|
+
self.material_names.append(mat_name)
|
|
111
|
+
|
|
112
|
+
self.faces = []
|
|
113
|
+
self.face_boundary_conditions = []
|
|
114
|
+
if surfaces and surface_boundary_conditions:
|
|
115
|
+
for i, (s, bc) in enumerate(
|
|
116
|
+
zip(surfaces, surface_boundary_conditions, strict=True)
|
|
117
|
+
):
|
|
118
|
+
s_wrapped = (
|
|
119
|
+
s
|
|
120
|
+
if isinstance(s, (TopoDS_Face, TopoDS_Shell))
|
|
121
|
+
else getattr(s, "wrapped", None)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if s.wrapped is None: # type: ignore
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"{s} {i} has no wrapped TopoDS_Shape. Is it valid?"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if isinstance(s_wrapped, TopoDS_Face):
|
|
130
|
+
self.faces.append(s_wrapped)
|
|
131
|
+
self.face_boundary_conditions.append(bc)
|
|
132
|
+
elif isinstance(s_wrapped, TopoDS_Shell):
|
|
133
|
+
child_faces = self._get_child_shapes(s_wrapped, TopoDS_Face)
|
|
134
|
+
self.faces.extend(child_faces)
|
|
135
|
+
self.face_boundary_conditions.extend([bc] * len(child_faces))
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
raise TypeError(
|
|
139
|
+
f"Surface {i} is of invalid type {type(s).__name__}"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
@overload
|
|
144
|
+
def _get_child_shapes(
|
|
145
|
+
parent: TopoDS_Shape, shape_type: Type[TopoDS_Face]
|
|
146
|
+
) -> list[TopoDS_Face]: ...
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
@overload
|
|
150
|
+
def _get_child_shapes(
|
|
151
|
+
parent: TopoDS_Shape, shape_type: Type[TopoDS_Shell]
|
|
152
|
+
) -> list[TopoDS_Shell]: ...
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
@overload
|
|
156
|
+
def _get_child_shapes(
|
|
157
|
+
parent: TopoDS_Shape, shape_type: Type[TopoDS_Solid]
|
|
158
|
+
) -> list[TopoDS_Solid]: ...
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _get_child_shapes(
|
|
162
|
+
parent: TopoDS_Shape, shape_type: Type[TopoDS_Shape]
|
|
163
|
+
) -> Sequence[TopoDS_Shape]:
|
|
164
|
+
"""Return all the child shapes of this shape."""
|
|
165
|
+
type_map = {
|
|
166
|
+
"TopoDS_Face": TopAbs_ShapeEnum.TopAbs_FACE,
|
|
167
|
+
"TopoDS_Shell": TopAbs_ShapeEnum.TopAbs_SHELL,
|
|
168
|
+
"TopoDS_Solid": TopAbs_ShapeEnum.TopAbs_SOLID,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
top_abs_type = type_map.get(shape_type.__name__)
|
|
172
|
+
if top_abs_type is None:
|
|
173
|
+
raise ValueError(f"Unsupported shape_type: {shape_type}")
|
|
174
|
+
|
|
175
|
+
shapes = []
|
|
176
|
+
explorer = TopExp_Explorer(parent, top_abs_type)
|
|
177
|
+
while explorer.More():
|
|
178
|
+
assert explorer.Current().ShapeType() == top_abs_type
|
|
179
|
+
shapes.append(explorer.Current())
|
|
180
|
+
explorer.Next()
|
|
181
|
+
return shapes
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def _get_solids_from_shape(cls, shape: TopoDS_Shape) -> list[TopoDS_Solid]:
|
|
185
|
+
"""Return all the solids in this shape."""
|
|
186
|
+
solids: list[TopoDS_Solid] = []
|
|
187
|
+
if shape.ShapeType() == TopAbs_ShapeEnum.TopAbs_SOLID:
|
|
188
|
+
solids.append(TopoDS.Solid_s(shape))
|
|
189
|
+
elif shape.ShapeType() == TopAbs_ShapeEnum.TopAbs_COMPOUND:
|
|
190
|
+
solids = cls._get_child_shapes(shape, TopoDS_Solid)
|
|
191
|
+
return solids
|
|
192
|
+
|
|
193
|
+
# TODO(akoen): from_step and from_brep are not DRY
|
|
194
|
+
# https://github.com/Thea-Energy/stellarmesh/issues/2
|
|
195
|
+
@classmethod
|
|
196
|
+
def from_step(
|
|
197
|
+
cls,
|
|
198
|
+
filename: str,
|
|
199
|
+
material_names: Sequence[str],
|
|
200
|
+
) -> Geometry:
|
|
201
|
+
"""Import model from a step file.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
filename: File path to import.
|
|
205
|
+
material_names: Ordered list of material names matching solids in file.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Model.
|
|
209
|
+
"""
|
|
210
|
+
logger.info(f"Importing {filename}")
|
|
211
|
+
|
|
212
|
+
reader = STEPControl_Reader()
|
|
213
|
+
read_status = reader.ReadFile(filename)
|
|
214
|
+
if read_status != IFSelect_RetDone:
|
|
215
|
+
raise ValueError(f"STEP File {filename} could not be loaded")
|
|
216
|
+
for i in range(reader.NbRootsForTransfer()):
|
|
217
|
+
reader.TransferRoot(i + 1)
|
|
218
|
+
|
|
219
|
+
solids = []
|
|
220
|
+
for i in range(reader.NbShapes()):
|
|
221
|
+
shape = reader.Shape(i + 1)
|
|
222
|
+
solids.extend(cls._get_solids_from_shape(shape))
|
|
223
|
+
|
|
224
|
+
return cls(solids, material_names)
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def import_step(
|
|
228
|
+
cls,
|
|
229
|
+
filename: str,
|
|
230
|
+
material_names: Sequence[str],
|
|
231
|
+
) -> Geometry:
|
|
232
|
+
"""Import model from a step file.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
filename: File path to import.
|
|
236
|
+
material_names: Ordered list of material names matching solids in file.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Model.
|
|
240
|
+
"""
|
|
241
|
+
warnings.warn(
|
|
242
|
+
"The import_step method is deprecated. Use from_step instead.",
|
|
243
|
+
FutureWarning,
|
|
244
|
+
stacklevel=2,
|
|
245
|
+
)
|
|
246
|
+
return cls.from_step(filename, material_names)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def from_brep(
|
|
250
|
+
cls,
|
|
251
|
+
filename: str,
|
|
252
|
+
material_names: Sequence[str],
|
|
253
|
+
) -> Geometry:
|
|
254
|
+
"""Import model from a brep (cadquery, build123d native) file.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
filename: File path to import.
|
|
258
|
+
material_names: Ordered list of material names matching solids in file.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Model.
|
|
262
|
+
"""
|
|
263
|
+
logger.info(f"Importing {filename}")
|
|
264
|
+
|
|
265
|
+
shape = TopoDS_Shape()
|
|
266
|
+
builder = BRep_Builder()
|
|
267
|
+
BRepTools.Read_s(shape, filename, builder)
|
|
268
|
+
|
|
269
|
+
if shape.IsNull():
|
|
270
|
+
raise ValueError(f"Could not import {filename}")
|
|
271
|
+
solids = cls._get_solids_from_shape(shape)
|
|
272
|
+
|
|
273
|
+
logger.info(f"Importing {len(solids)} from {filename}")
|
|
274
|
+
return cls(solids, material_names)
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def import_brep(
|
|
278
|
+
cls,
|
|
279
|
+
filename: str,
|
|
280
|
+
material_names: Sequence[str],
|
|
281
|
+
) -> Geometry:
|
|
282
|
+
"""Import model from a brep (cadquery, build123d native) file.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
filename: File path to import.
|
|
286
|
+
material_names: Ordered list of material names matching solids in file.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Model.
|
|
290
|
+
"""
|
|
291
|
+
warnings.warn(
|
|
292
|
+
"The import_brep method is deprecated. Use from_brep instead.",
|
|
293
|
+
FutureWarning,
|
|
294
|
+
stacklevel=2,
|
|
295
|
+
)
|
|
296
|
+
return cls.from_brep(filename, material_names)
|
|
297
|
+
|
|
298
|
+
def imprint(self) -> Geometry:
|
|
299
|
+
"""Imprint faces of current geometry.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
A new geometry with the imprinted and merged geometry.
|
|
303
|
+
"""
|
|
304
|
+
bldr = BOPAlgo_MakeConnected()
|
|
305
|
+
bldr.SetRunParallel(theFlag=True)
|
|
306
|
+
bldr.SetUseOBB(theUseOBB=True)
|
|
307
|
+
|
|
308
|
+
for solid in self.solids:
|
|
309
|
+
bldr.AddArgument(solid)
|
|
310
|
+
|
|
311
|
+
bldr.Perform()
|
|
312
|
+
res = bldr.Shape()
|
|
313
|
+
res_solids = self._get_solids_from_shape(res)
|
|
314
|
+
|
|
315
|
+
if (l0 := len(res_solids)) != (l1 := len(self.solids)):
|
|
316
|
+
raise RuntimeError(
|
|
317
|
+
f"Length of imprinted solids {l0} != length of original solids {l1}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return type(self)(res_solids, self.material_names)
|