fix: ensure renderer is initialized before operations
- Add _ensure_initialized() method to Viewer3DWidget - Queue pending meshes until widget is shown - Add remove_mesh() method to PygfxRenderer - Fix all viewer methods to check initialization
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -650,43 +650,62 @@ class Viewer3DWidget(QWidget):
|
|||||||
self._meshes: Dict[str, Any] = {}
|
self._meshes: Dict[str, Any] = {}
|
||||||
self._selected_normal: Optional[Tuple[float, float, float]] = None
|
self._selected_normal: Optional[Tuple[float, float, float]] = None
|
||||||
self._centroid: Optional[Tuple[float, float, float]] = None
|
self._centroid: Optional[Tuple[float, float, float]] = None
|
||||||
|
self._pending_meshes: List[Tuple] = []
|
||||||
|
|
||||||
def showEvent(self, event):
|
def showEvent(self, event):
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
self._renderer.initialize(self)
|
self._renderer.initialize(self)
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
for args in self._pending_meshes:
|
||||||
|
self.add_mesh(*args)
|
||||||
|
self._pending_meshes.clear()
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
|
def _ensure_initialized(self):
|
||||||
|
if not self._initialized:
|
||||||
|
self._renderer.initialize(self)
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
def get_renderer(self) -> PygfxRenderer:
|
def get_renderer(self) -> PygfxRenderer:
|
||||||
return self._renderer
|
return self._renderer
|
||||||
|
|
||||||
def add_mesh(self, vertices, faces, color=None, name=None) -> str:
|
def add_mesh(self, vertices, faces, color=None, name=None) -> str:
|
||||||
|
if not self._initialized:
|
||||||
|
self._pending_meshes.append((vertices, faces, color, name))
|
||||||
|
return f"pending_{len(self._pending_meshes)}"
|
||||||
|
|
||||||
|
self._ensure_initialized()
|
||||||
mesh_id = self._renderer.add_mesh(vertices, faces, color, name)
|
mesh_id = self._renderer.add_mesh(vertices, faces, color, name)
|
||||||
self._meshes[mesh_id] = {"vertices": vertices, "faces": faces, "name": name}
|
self._meshes[mesh_id] = {"vertices": vertices, "faces": faces, "name": name}
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
return mesh_id
|
return mesh_id
|
||||||
|
|
||||||
def update_mesh(self, mesh_id: str, vertices, faces):
|
def update_mesh(self, mesh_id: str, vertices, faces):
|
||||||
|
self._ensure_initialized()
|
||||||
self._renderer.update_mesh(mesh_id, vertices, faces)
|
self._renderer.update_mesh(mesh_id, vertices, faces)
|
||||||
self._meshes[mesh_id] = {"vertices": vertices, "faces": faces}
|
self._meshes[mesh_id] = {"vertices": vertices, "faces": faces}
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
def remove_mesh(self, mesh_id: str):
|
def remove_mesh(self, mesh_id: str):
|
||||||
|
self._ensure_initialized()
|
||||||
self._renderer.remove_mesh(mesh_id)
|
self._renderer.remove_mesh(mesh_id)
|
||||||
if mesh_id in self._meshes:
|
if mesh_id in self._meshes:
|
||||||
del self._meshes[mesh_id]
|
del self._meshes[mesh_id]
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
def clear_scene(self):
|
def clear_scene(self):
|
||||||
|
self._ensure_initialized()
|
||||||
self._renderer.clear_scene()
|
self._renderer.clear_scene()
|
||||||
self._meshes.clear()
|
self._meshes.clear()
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
def fit_camera(self):
|
def fit_camera(self):
|
||||||
|
self._ensure_initialized()
|
||||||
self._renderer.fit_camera()
|
self._renderer.fit_camera()
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
def set_camera_position(self, position, target):
|
def set_camera_position(self, position, target):
|
||||||
|
self._ensure_initialized()
|
||||||
self._renderer.set_camera_position(position, target)
|
self._renderer.set_camera_position(position, target)
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -264,6 +264,13 @@ class PygfxRenderer(Renderer):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def remove_mesh(self, mesh_id: str) -> bool:
|
||||||
|
"""Remove a mesh by its ID."""
|
||||||
|
for obj in self._objects:
|
||||||
|
if obj.name == mesh_id:
|
||||||
|
return self.remove_object(obj)
|
||||||
|
return False
|
||||||
|
|
||||||
def clear_scene(self) -> None:
|
def clear_scene(self) -> None:
|
||||||
"""Remove all objects from the scene."""
|
"""Remove all objects from the scene."""
|
||||||
for obj in self._objects[:]:
|
for obj in self._objects[:]:
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: fluency-cad
|
||||||
|
Version: 2.0.0
|
||||||
|
Summary: Parametric CAD application with OpenCASCADE geometry kernel
|
||||||
|
Author: Fluency CAD Team
|
||||||
|
License: MIT
|
||||||
|
Project-URL: Homepage, https://github.com/fluency-cad/fluency
|
||||||
|
Project-URL: Documentation, https://github.com/fluency-cad/fluency#readme
|
||||||
|
Project-URL: Repository, https://github.com/fluency-cad/fluency
|
||||||
|
Keywords: cad,parametric,opencascade,3d-modeling
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: End Users/Desktop
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Topic :: Scientific/Engineering :: CAD
|
||||||
|
Requires-Python: >=3.10
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: cadquery>=2.4
|
||||||
|
Requires-Dist: pygfx>=0.1.0
|
||||||
|
Requires-Dist: wgpu>=0.1.0
|
||||||
|
Requires-Dist: PySide6>=6.4.0
|
||||||
|
Requires-Dist: numpy>=1.24.0
|
||||||
|
Requires-Dist: scipy>=1.10.0
|
||||||
|
Requires-Dist: pillow>=10.0.0
|
||||||
|
Provides-Extra: dev
|
||||||
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
||||||
|
Requires-Dist: black>=24.0; extra == "dev"
|
||||||
|
Requires-Dist: mypy>=1.8; extra == "dev"
|
||||||
|
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
||||||
|
|
||||||
|
# Fluency CAD 2.0
|
||||||
|
|
||||||
|
A parametric CAD application built on OpenCASCADE Technology (OCCT) with a modern pygfx-based 3D renderer.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **OpenCASCADE Geometry Kernel**: Industry-standard BRep geometry with exact precision
|
||||||
|
- **STEP/IGES Import/Export**: Full support for industry-standard CAD file formats
|
||||||
|
- **Parametric Sketching**: 2D sketching with constraint solving using CadQuery
|
||||||
|
- **Boolean Operations**: Union, difference, and intersection
|
||||||
|
- **Fillet & Chamfer**: Apply edge treatments to solid bodies
|
||||||
|
- **Modern Renderer**: WebGPU-based rendering with pygfx (smaller footprint than VTK)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
fluency/
|
||||||
|
├── src/fluency/
|
||||||
|
│ ├── geometry/ # Geometry abstraction layer
|
||||||
|
│ │ └── base.py # Abstract interfaces
|
||||||
|
│ ├── geometry_occ/ # OpenCASCADE implementation
|
||||||
|
│ │ ├── kernel.py # OCGeometryKernel
|
||||||
|
│ │ └── sketch.py # OCCSketch with constraints
|
||||||
|
│ ├── rendering/ # Rendering abstraction
|
||||||
|
│ │ ├── base.py # Abstract renderer
|
||||||
|
│ │ └── pygfx_renderer.py
|
||||||
|
│ ├── models/ # Data models
|
||||||
|
│ │ └── data_model.py # Project, Component, Sketch, Body
|
||||||
|
│ └── main.py # Application entry point
|
||||||
|
├── tests/
|
||||||
|
│ └── test_geometry.py
|
||||||
|
└── pyproject.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create virtual environment
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Package | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| cadquery | High-level OpenCASCADE Python bindings |
|
||||||
|
| ocp | Low-level OpenCASCADE Python bindings |
|
||||||
|
| pygfx | WebGPU-based 3D renderer |
|
||||||
|
| wgpu | WebGPU Python bindings |
|
||||||
|
| PySide6 | Qt GUI framework |
|
||||||
|
| numpy | Numerical computing |
|
||||||
|
| scipy | Scientific computing |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the application
|
||||||
|
fluency-cad
|
||||||
|
|
||||||
|
# Or directly
|
||||||
|
python -m fluency.main
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fluency.geometry_occ.kernel import OCGeometryKernel
|
||||||
|
from fluency.geometry.base import Point2D
|
||||||
|
|
||||||
|
# Create kernel
|
||||||
|
kernel = OCGeometryKernel()
|
||||||
|
|
||||||
|
# Create a sketch
|
||||||
|
points = [
|
||||||
|
Point2D(0, 0),
|
||||||
|
Point2D(10, 0),
|
||||||
|
Point2D(10, 10),
|
||||||
|
Point2D(0, 10),
|
||||||
|
]
|
||||||
|
polygon = kernel.create_polygon(points)
|
||||||
|
|
||||||
|
# Extrude to 3D
|
||||||
|
body = kernel.extrude(polygon, height=20.0)
|
||||||
|
|
||||||
|
# Apply fillet
|
||||||
|
body = kernel.fillet(body, radius=2.0)
|
||||||
|
|
||||||
|
# Export to STEP
|
||||||
|
kernel.export_step(body, "part.step")
|
||||||
|
|
||||||
|
# Export to STL
|
||||||
|
kernel.export_stl(body, "part.stl")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison: Before vs After
|
||||||
|
|
||||||
|
| Aspect | Before (SDF + VTK) | After (OCC + pygfx) |
|
||||||
|
|--------|-------------------|---------------------|
|
||||||
|
| Geometry Precision | Approximate (mesh) | Exact (BRep) |
|
||||||
|
| Export Formats | STL only | STEP, IGES, STL, BREP |
|
||||||
|
| File Size | Large (mesh) | Small (BRep) |
|
||||||
|
| Fillet/Chamfer | Approximate | Exact |
|
||||||
|
| Dependency Size | ~200MB (VTK) | ~30MB (pygfx) |
|
||||||
|
| Constraint Solver | SolveSpace (separate) | CadQuery (integrated) |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
src/fluency/__init__.py
|
||||||
|
src/fluency/main.py
|
||||||
|
src/fluency/geometry/__init__.py
|
||||||
|
src/fluency/geometry/base.py
|
||||||
|
src/fluency/geometry_occ/__init__.py
|
||||||
|
src/fluency/geometry_occ/kernel.py
|
||||||
|
src/fluency/geometry_occ/sketch.py
|
||||||
|
src/fluency/models/__init__.py
|
||||||
|
src/fluency/models/data_model.py
|
||||||
|
src/fluency/rendering/__init__.py
|
||||||
|
src/fluency/rendering/base.py
|
||||||
|
src/fluency/rendering/pygfx_renderer.py
|
||||||
|
src/fluency/utils/__init__.py
|
||||||
|
src/fluency/widgets/__init__.py
|
||||||
|
src/fluency_cad.egg-info/PKG-INFO
|
||||||
|
src/fluency_cad.egg-info/SOURCES.txt
|
||||||
|
src/fluency_cad.egg-info/dependency_links.txt
|
||||||
|
src/fluency_cad.egg-info/entry_points.txt
|
||||||
|
src/fluency_cad.egg-info/requires.txt
|
||||||
|
src/fluency_cad.egg-info/top_level.txt
|
||||||
|
tests/test_geometry.py
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
fluency-cad = fluency.main:main
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
cadquery>=2.4
|
||||||
|
pygfx>=0.1.0
|
||||||
|
wgpu>=0.1.0
|
||||||
|
PySide6>=6.4.0
|
||||||
|
numpy>=1.24.0
|
||||||
|
scipy>=1.10.0
|
||||||
|
pillow>=10.0.0
|
||||||
|
|
||||||
|
[dev]
|
||||||
|
pytest>=8.0
|
||||||
|
black>=24.0
|
||||||
|
mypy>=1.8
|
||||||
|
ruff>=0.4.0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
fluency
|
||||||
Binary file not shown.
Reference in New Issue
Block a user