Skip to content

Commit 03998b7

Browse files
authored
Merge pull request #6 from LIHPC-Computational-Geometry/feature/ferrispline-submodule
Feature/ferrispline submodule
2 parents 7ec79df + fb34f5b commit 03998b7

33 files changed

Lines changed: 1246 additions & 465 deletions

.github/workflows/lint.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,35 @@ jobs:
66
ruff:
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/checkout@v4
9+
- uses: actions/checkout@v5
10+
with:
11+
submodules: true
1012

1113
# for gmsh and panda3D
1214
- name: Install system graphics dependencies
1315
run: |
1416
sudo apt-get update
1517
sudo apt-get install -y libglu1-mesa libgl1-mesa-dev libosmesa6
1618
19+
- name: Install Rust
20+
uses: dtolnay/rust-toolchain@stable
21+
22+
- name: Cache Rust
23+
uses: Swatinem/rust-cache@v2
24+
with:
25+
workspaces: "ferrispline"
26+
1727
- name: Install uv
1828
uses: astral-sh/setup-uv@v3
29+
with:
30+
enable-cache: true
31+
cache-dependency-glob: "uv.lock"
1932

2033
- name: Set up Python
2134
run: uv python install
2235

2336
- name: Install dependencies
24-
run: uv sync --all-extras --dev --find-links wheel/
37+
run: uv sync --all-extras --dev
2538

2639
- name: Run Ruff Check
2740
# Vérifie les erreurs de code et de logique

.github/workflows/tests.yml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,44 @@ jobs:
66
test:
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/checkout@v4
9+
- uses: actions/checkout@v5
10+
with:
11+
submodules: true
1012

1113
# for gmsh and panda3D
1214
- name: Install system graphics dependencies
1315
run: |
1416
sudo apt-get update
1517
sudo apt-get install -y libglu1-mesa libgl1-mesa-dev libosmesa6
1618
19+
- name: Install Rust
20+
uses: dtolnay/rust-toolchain@stable
21+
22+
- name: Cache Rust
23+
uses: Swatinem/rust-cache@v2
24+
with:
25+
workspaces: "ferrispline"
26+
1727
- name: Install uv
1828
uses: astral-sh/setup-uv@v3
29+
with:
30+
enable-cache: true
31+
cache-dependency-glob: "uv.lock"
1932

2033
- name: Set up Python
2134
run: uv python install
2235

2336
- name: Install dependencies
24-
run: uv sync --all-extras --dev --find-links wheel/
37+
run: uv sync --all-extras --dev
2538

2639
- name: Run tests with coverage
2740
# On génère un rapport XML pour Codecov
2841
run: uv run pytest --cov=. --cov-report=xml
2942

30-
- name: Upload coverage to Codecov
31-
uses: codecov/codecov-action@v4
32-
with:
33-
token: ${{ secrets.CODECOV_TOKEN }}
34-
file: ./coverage.xml
35-
fail_ci_if_error: true
43+
# FIXME: need access to the organization
44+
# - name: Upload coverage to Codecov
45+
# uses: codecov/codecov-action@v4
46+
# with:
47+
# token: ${{ secrets.CODECOV_TOKEN }}
48+
# file: ./coverage.xml
49+
# fail_ci_if_error: true

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "ferrispline"]
2+
path = ferrispline
3+
url = https://github.com/LIHPC-Computational-Geometry/ferrispline

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ We use [uv](https://docs.astral.sh/uv/) for all dependency and environment manag
122122
### 1. Clone and set up
123123

124124
```bash
125-
git clone https://github.com/franck-ledoux/bot.git
125+
git clone --recurse-submodules https://github.com/franck-ledoux/bot.git
126126
cd bot
127127
uv sync # creates .venv and installs all production + dev dependencies
128128
```
@@ -193,6 +193,7 @@ bot/
193193
│ └── viewer/
194194
│ ├── viewer.py # Viewer — public API, manages the subprocess
195195
│ └── app.py # ViewerApp — Panda3D ShowBase (runs in subprocess)
196+
|── ferrispline/ # Submodule library for generating, manipulating and computing hexahedral meshes
196197
├── tests/
197198
│ ├── unit/ # Isolated class tests (no display required)
198199
│ └── system/ # End-to-end workflow tests

bot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
CADModel = None
2525
Model = CADModel
2626

27-
__all__ = ['core', 'control', 'view', 'viewer', 'Model', 'Viewer']
27+
__all__ = ["core", "control", "view", "viewer", "Model", "Viewer"]

bot/control/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
"""
2-
Control module: This submodule of bot gathers classes and functions for controling the view module and make the link
2+
Control module: This submodule of bot gathers classes and functions for controling the view module and make the link
33
between the view and the model
44
"""
5-
6-
from . import *

bot/control/camera.py

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from panda3d.core import LPoint3, LVector3, OrthographicLens, LineSegs, NodePath
22
from direct.interval.IntervalGlobal import Parallel, LerpFunc, Sequence, Func
33

4+
45
class CameraController:
56
"""
67
Orthographic camera controller for the 3D view.
@@ -11,7 +12,7 @@ class CameraController:
1112
``cmd_zoom``, ``cmd_center`` and ``cmd_align_plane``.
1213
"""
1314

14-
#TODO: check settings maybe a configuration error
15+
# TODO: check settings maybe a configuration error
1516
def __init__(self, base, scene, settings):
1617
"""
1718
Set up the orthographic camera and its node hierarchy.
@@ -51,11 +52,10 @@ def __init__(self, base, scene, settings):
5152
self.key_pan_speed = 2.0
5253

5354
# Task pour maintenir le marqueur et le gizmo
54-
taskMgr.add(self.update_task, "CameraUpdateTask")
55+
self.taskMgr.add(self.update_task, "CameraUpdateTask")
5556
# Configure la camera par rapport au contenu de la scene
5657
self.refresh_scene()
5758

58-
5959
def refresh_scene(self):
6060
"""
6161
Recompute camera parameters from the current scene geometry.
@@ -67,7 +67,8 @@ def refresh_scene(self):
6767
bounds = self.scene.geom_node.getBounds()
6868
center = bounds.getCenter()
6969
radius = bounds.getRadius()
70-
if radius <= 0: radius = 1 # Sécurité si modèle vide
70+
if radius <= 0:
71+
radius = 1 # Sécurité si modèle vide
7172

7273
# 2. Positionner le pivot au centre de l'objet
7374
self.focal_node.setPos(center)
@@ -100,10 +101,12 @@ def create_marker(self):
100101
"""
101102
ls = LineSegs()
102103
ls.setThickness(2)
103-
for i, col in enumerate([(1,0,0,1), (0,1,0,1), (0,0,1,1)]):
104+
for i, col in enumerate([(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]):
104105
ls.setColor(col)
105-
v = LVector3(0,0,0); v[i] = 0.5
106-
ls.moveTo(0,0,0); ls.drawTo(v)
106+
v = LVector3(0, 0, 0)
107+
v[i] = 0.5
108+
ls.moveTo(0, 0, 0)
109+
ls.drawTo(v)
107110
return NodePath(ls.create())
108111

109112
def handle_rotate(self, dx, dy):
@@ -114,7 +117,8 @@ def handle_rotate(self, dx, dy):
114117
dx: Normalised horizontal delta (screen space, -1..1).
115118
dy: Normalised vertical delta (screen space, -1..1).
116119
"""
117-
if self.is_animating: return
120+
if self.is_animating:
121+
return
118122
sens = 100.0
119123
new_h = self.focal_node.getH() - dx * sens
120124
new_p = self.focal_node.getP() + dy * sens
@@ -131,7 +135,8 @@ def handle_pan(self, dx, dy):
131135
dx: Normalised horizontal delta (screen space, -1..1).
132136
dy: Normalised vertical delta (screen space, -1..1).
133137
"""
134-
if self.is_animating: return
138+
if self.is_animating:
139+
return
135140

136141
# Largeur de la vue actuelle
137142
fs = self.lens.getFilmSize().getX()
@@ -155,7 +160,8 @@ def handle_zoom(self, factor):
155160
factor: Multiplier applied to the current film size
156161
(< 1 zooms in, > 1 zooms out).
157162
"""
158-
if self.is_animating: return
163+
if self.is_animating:
164+
return
159165

160166
# 1. Calculer la nouvelle taille de vue
161167
current_size = self.lens.getFilmSize().getX()
@@ -180,21 +186,20 @@ def recenter(self):
180186
Only the pivot position is animated; rotation and zoom are left
181187
unchanged so the user keeps their current viewing angle.
182188
"""
183-
if self.is_animating: return
189+
if self.is_animating:
190+
return
184191
self.is_animating = True
185192

186193
# On récupère le centre réel de l'objet calculé dans analyze_model
187194
# target_pos est un LPoint3 (le centre géométrique du modèle)
188195
target_pos = self.model_center
189196

190-
duration = 0.4 # Un peu plus rapide pour un recadrage fluide
197+
duration = 0.4 # Un peu plus rapide pour un recadrage fluide
191198

192199
# On ne crée qu'UN SEUL intervalle : la position.
193200
# On ne touche NI au HPR (rotation) NI au FilmSize (zoom).
194201
self.cam_anim = self.focal_node.posInterval(
195-
duration,
196-
target_pos,
197-
blendType='easeInOut'
202+
duration, target_pos, blendType="easeInOut"
198203
)
199204
# Séquence pour déverrouiller à la fin
200205
Sequence(self.cam_anim, Func(self._unlock)).start()
@@ -214,43 +219,46 @@ def align_to_plane(self, axis):
214219
axis: One of ``"x"`` (right view), ``"y"`` (front view) or
215220
``"z"`` (top view).
216221
"""
217-
if self.is_animating: return
222+
if self.is_animating:
223+
return
218224

219225
# 1. Définir les rotations cibles (Heading, Pitch, Roll)
220-
if axis == "z": # Vue de dessus (Top)
226+
if axis == "z": # Vue de dessus (Top)
221227
target_hpr = LPoint3(0, -90, 0)
222-
elif axis == "y": # Vue de face (Front)
228+
elif axis == "y": # Vue de face (Front)
223229
target_hpr = LPoint3(0, 0, 0)
224-
elif axis == "x": # Vue de côté (Right)
230+
elif axis == "x": # Vue de côté (Right)
225231
target_hpr = LPoint3(90, 0, 0)
226232
else:
227233
return
228234

229235
self.is_animating = True
230236

231237
duration = 0.5
232-
# 2. Animation fluide de la rotation du pivot
238+
# 2. Animation fluide de la rotation du pivot
233239
# On peut aussi combiner cela avec un recentrage automatique
234-
center = LPoint3(*self.scene.bounds['center'])
235-
max_dim = max(self.scene.bounds['size']) if max(self.scene.bounds['size']) > 0 else 1.0
240+
center = LPoint3(*self.scene.bounds["center"])
241+
max_dim = (
242+
max(self.scene.bounds["size"])
243+
if max(self.scene.bounds["size"]) > 0
244+
else 1.0
245+
)
236246
self.transition = Parallel(
237247
# 1. Aligne la rotation sur l'axe demandé
238-
self.focal_node.hprInterval(duration, target_hpr, blendType='easeInOut'),
239-
248+
self.focal_node.hprInterval(duration, target_hpr, blendType="easeInOut"),
240249
# 2. Déplace le pivot vers le centre réel du modèle
241-
self.focal_node.posInterval(duration, center, blendType='easeInOut'),
242-
LerpFunc(lambda s: self.lens.setFilmSize(s),
243-
fromData=self.lens.getFilmSize().getX(),
244-
toData=max_dim * 1.5,
245-
duration=duration,
246-
blendType='easeInOut'),
250+
self.focal_node.posInterval(duration, center, blendType="easeInOut"),
251+
LerpFunc(
252+
lambda s: self.lens.setFilmSize(s),
253+
fromData=self.lens.getFilmSize().getX(),
254+
toData=max_dim * 1.5,
255+
duration=duration,
256+
blendType="easeInOut",
257+
),
247258
)
248259

249260
# On lance et on déverrouille à la fin
250-
Sequence(
251-
self.transition,
252-
Func(self._unlock)
253-
).start()
261+
Sequence(self.transition, Func(self._unlock)).start()
254262

255263
# Parallel(
256264
# self.focal_node.hprInterval(duration, target_hpr, blendType='easeInOut'),
@@ -265,7 +273,6 @@ def align_to_plane(self, axis):
265273

266274
# taskMgr.doMethodLater(duration, self._unlock, "UnlockTask")
267275

268-
269276
def update_task(self, task):
270277
"""
271278
Per-frame task: keep the pivot marker and gizmo in sync with the camera.
@@ -276,7 +283,6 @@ def update_task(self, task):
276283
# Le marqueur suit le pivot
277284
self.marker.setPos(self.focal_node.getPos())
278285
# Mise à jour du Gizmo (orientation de la caméra vers le monde)
279-
if hasattr(self.scene, 'gizmo'):
286+
if hasattr(self.scene, "gizmo"):
280287
self.scene.gizmo.update(self.base.camera.getQuat(self.base.render))
281288
return task.cont
282-

0 commit comments

Comments
 (0)