Skip to content

Commit ebb8535

Browse files
authored
Merge pull request #46 from petrasvestartas/remesh_dual
ADD remesh_dual
2 parents 48e1347 + 8474804 commit ebb8535

File tree

6 files changed

+663
-35
lines changed

6 files changed

+663
-35
lines changed

.github/workflows/build.yml

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,81 @@ on:
1010

1111
jobs:
1212
Build:
13-
if: "!contains(github.event.pull_request.labels.*.name, 'docs-only')"
13+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'docs-only') }}
1414
runs-on: ${{ matrix.os }}
1515
strategy:
1616
matrix:
1717
os: [ubuntu-latest, macos-latest, windows-latest]
1818
python: ["3.10"]
1919

2020
steps:
21+
- uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Set up conda
26+
uses: conda-incubator/setup-miniconda@v3
27+
with:
28+
python-version: ${{ matrix.python }}
29+
channels: conda-forge
30+
activate-environment: "compas_123-dev"
31+
32+
- name: Configure conda channels
33+
shell: bash -el {0}
34+
run: |
35+
conda config --add channels defaults
36+
conda config --set channel_priority strict
37+
2138
- uses: compas-dev/compas-actions.build@v4
2239
with:
2340
invoke_lint: true
2441
use_conda: true
2542
check_import: true
2643
python: ${{ matrix.python }}
44+
45+
build_wheels:
46+
name: cibuildwheel on ${{ matrix.platform }}
47+
needs: [Build] # Only run if Build job succeeds
48+
runs-on: ${{ matrix.os }}
49+
strategy:
50+
fail-fast: false
51+
matrix:
52+
include:
53+
- os: ubuntu-latest
54+
platform: manylinux
55+
- os: macos-latest
56+
platform: mac
57+
- os: windows-latest
58+
platform: windows
59+
60+
steps:
61+
- uses: actions/checkout@v4
62+
with:
63+
fetch-depth: 0
64+
65+
- name: Install cibuildwheel
66+
run: pipx install cibuildwheel==2.23.1
67+
68+
- name: Build wheels
69+
run: cibuildwheel --output-dir wheelhouse .
70+
71+
- uses: actions/upload-artifact@v4
72+
with:
73+
name: wheels-${{ matrix.platform }}
74+
path: wheelhouse/*.whl
75+
76+
build_sdist:
77+
name: Test source distribution
78+
runs-on: ubuntu-latest
79+
steps:
80+
- uses: actions/checkout@v4
81+
with:
82+
fetch-depth: 0
83+
84+
- name: Build SDist
85+
run: pipx run build --sdist
86+
87+
- uses: actions/upload-artifact@v4
88+
with:
89+
name: sdist
90+
path: dist/*.tar.gz

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
### Added
2020

21+
* Added `compas_cgal.meshing.mesh_remesh_dual`.
22+
2123
### Changed
2224

2325
### Removed

docs/examples/example_meshing.py

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,35 @@
1-
import math
2-
from pathlib import Path
3-
1+
from compas.geometry import Sphere
42
from compas.datastructures import Mesh
5-
from compas.geometry import Rotation
6-
from compas.geometry import Scale
73
from compas.geometry import Translation
8-
from compas.geometry import Vector
9-
from compas.geometry import scale_vector
104
from compas.geometry import transform_points_numpy
115
from compas_viewer import Viewer
126

13-
from compas_cgal.meshing import mesh_remesh
14-
7+
from compas_cgal.meshing import mesh_remesh, mesh_dual
158

169
def main():
17-
"""Remesh a bunny mesh that is loaded from .ply file."""
18-
19-
FILE = Path(__file__).parent.parent.parent / "data" / "Bunny.ply"
20-
bunny = Mesh.from_ply(FILE)
21-
bunny.remove_unused_vertices()
22-
23-
centroid = bunny.vertex_point(0)
24-
25-
vector = scale_vector(centroid, -1)
10+
"""Remesh a mesh."""
2611

27-
T = Translation.from_vector(vector)
28-
S = Scale.from_factors([100, 100, 100])
29-
R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90))
12+
mesh = Mesh.from_shape(Sphere(1))
13+
mesh.quads_to_triangles()
3014

31-
# bunny.transform(R * S * T)
32-
V0, F0 = bunny.to_vertices_and_faces()
33-
V1 = transform_points_numpy(V0, R * S * T)
15+
edge_length = 0.5
16+
iterations = 10
17+
x_translation = 2
3418

35-
V1, F1 = mesh_remesh((V0, F0), 0.3, 10)
36-
V1 = transform_points_numpy(V1, Translation.from_vector([20, 0, 0]))
37-
mesh = Mesh.from_vertices_and_faces(V1, F1)
19+
# Remesh
20+
V1, F1 = mesh_remesh(mesh.to_vertices_and_faces(), edge_length, iterations)
21+
V1 = transform_points_numpy(V1, Translation.from_vector([x_translation, 0, 0]))
22+
remeshed = Mesh.from_vertices_and_faces(V1, F1)
23+
24+
# Dual
25+
V2, F2 = mesh_dual((V1, F1), 2.14, True)
26+
V2 = transform_points_numpy(V2, Translation.from_vector([x_translation, 0, 0]))
27+
dual = Mesh.from_vertices_and_faces(V2, F2)
3828

39-
return bunny, mesh
29+
return mesh, remeshed, dual
4030

4131

42-
bunny, mesh = main()
32+
mesh, remeshed, dual = main()
4333

4434

4535
# ==============================================================================
@@ -49,9 +39,10 @@ def main():
4939
viewer = Viewer(width=1600, height=900)
5040

5141
viewer.renderer.camera.target = [0, 0, 0]
52-
viewer.renderer.camera.position = [0, -25, 10]
42+
viewer.renderer.camera.position = [0, -0.25, 0.10]
5343

54-
viewer.scene.add(bunny, show_points=False)
5544
viewer.scene.add(mesh, show_points=False)
45+
viewer.scene.add(remeshed, show_points=False)
46+
viewer.scene.add(dual, show_points=True)
5647

5748
viewer.show()

src/compas_cgal/meshing.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from compas.plugins import plugin
33

44
from compas_cgal import _meshing
5+
from compas_cgal import _types_std # noqa: F401
56

67
from .types import VerticesFaces
78
from .types import VerticesFacesNumpy
@@ -51,4 +52,45 @@ def mesh_remesh(
5152
V, F = mesh
5253
V = np.asarray(V, dtype=np.float64, order="C")
5354
F = np.asarray(F, dtype=np.int32, order="C")
54-
return _meshing.remesh(V, F, target_edge_length, number_of_iterations)
55+
return _meshing.remesh(V, F, target_edge_length, number_of_iterations, do_project)
56+
57+
58+
def mesh_dual(
59+
mesh: VerticesFaces,
60+
angle_radians: float = 0.0,
61+
circumcenter: bool = True,
62+
scale_factor: float = 1.0,
63+
) -> tuple[np.ndarray, list[list[int]]]:
64+
"""Create a dual mesh from a triangular mesh with variable-length faces.
65+
66+
Parameters
67+
----------
68+
mesh : :attr:`compas_cgal.types.VerticesFaces`
69+
The mesh to create a dual from.
70+
angle_radians : double, optional
71+
Angle limit in radians for boundary vertices to remove.
72+
circumcenter : bool, optional
73+
Whether to use the circumcenter of the triangle instead of the centroid.
74+
scale_factor : double, optional
75+
Scale factor for inner vertices.
76+
77+
Returns
78+
-------
79+
tuple
80+
A tuple containing:
81+
- Dual mesh vertices as an Nx3 numpy array.
82+
- Variable-length faces as a list of lists of vertex indices.
83+
84+
Notes
85+
-----
86+
This dual mesh implementation includes proper boundary handling by:
87+
1. Creating vertices at face centroids of the primal mesh
88+
2. Creating additional vertices at boundary edge midpoints
89+
3. Creating proper connections for boundary edges
90+
91+
"""
92+
V, F = mesh
93+
V = np.asarray(V, dtype=np.float64, order="C")
94+
F = np.asarray(F, dtype=np.int32, order="C")
95+
vertices, var_faces = _meshing.dual(V, F, angle_radians, circumcenter, scale_factor)
96+
return vertices, var_faces

0 commit comments

Comments
 (0)