- added renderer

This commit is contained in:
bklronin
2026-07-12 22:21:20 +02:00
parent b8516fff91
commit 210e3cfb5d
12 changed files with 2628 additions and 1580 deletions
+120
View File
@@ -577,6 +577,126 @@ class OCCRenderer(Renderer):
return (_xyz(eye), _xyz(at), _xyz(up))
def get_camera_fov(self) -> float:
"""Return the current vertical FOV in degrees from the OCC camera."""
if self._view is None:
return 45.0
cam = self._view.Camera()
fov_y = cam.FOVy()
# Clamp to a reasonable range for Mitsuba (10120 deg)
return max(10.0, min(120.0, fov_y))
def get_render_camera(self) -> "RenderCamera":
"""Compute a :class:`RenderCamera` from the current viewport state.
Handles both perspective and orthographic projection modes:
* **Perspective** — uses the actual eye/target/up/fov directly.
* **Orthographic** — translates the view's scale factor (which encodes
mouse-wheel zoom) into a camera distance so the render framing
matches what the user sees in the viewport. In orthographic mode,
zooming changes ``view.Scale()`` rather than moving the eye position,
so simply reading ``Eye()`` produces incorrect framing.
Returns *None* if the view is not initialised.
"""
from .render_backend import RenderCamera
if self._view is None:
return None
cam = self._view.Camera()
eye_obj = self._view.Eye()
at_obj = self._view.At()
up_obj = self._view.Up()
def _xyz(v):
if isinstance(v, (tuple, list)):
return np.array([float(v[0]), float(v[1]), float(v[2])])
return np.array([v.X(), v.Y(), v.Z()])
eye = _xyz(eye_obj)
at = _xyz(at_obj)
up = _xyz(up_obj)
fov_y = max(10.0, min(120.0, cam.FOVy()))
# Check projection type.
from OCP.Graphic3d import Graphic3d_Camera
proj_type = cam.ProjectionType()
is_orthographic = (
proj_type == Graphic3d_Camera.Projection_Orthographic
)
if not is_orthographic:
# Perspective mode: use the actual eye position directly.
return RenderCamera(
origin=tuple(float(v) for v in eye),
target=tuple(float(v) for v in at),
up=tuple(float(v) for v in up),
fov=fov_y,
)
# ── Orthographic mode: translate scale → camera distance ───────
# In OCC orthographic projection, mouse-wheel zoom changes
# ``view.Scale()`` rather than moving the eye. A smaller scale
# means "zoomed in" (more world units per pixel), so the render
# camera should move closer to match.
view_scale = self._view.Scale()
# Compute scene bounding box diagonal from displayed objects.
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
bbox = Bnd_Box()
try:
for robj in self._objects.values():
if robj.ais_shape is not None and robj.ais_type != "workplane":
shape = getattr(robj.ais_shape, "Shape", lambda: None)()
if shape is not None:
BRepBndLib.Add_s(shape, bbox)
except Exception:
pass
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
diag = float(
np.sqrt(
(xmax - xmin) ** 2
+ (ymax - ymin) ** 2
+ (zmax - zmin) ** 2
)
)
# Fallback: if bbox is empty (no objects or all shapes failed),
# use the eye-to-at distance as a reasonable estimate.
if diag < 1e-9:
diag = float(np.linalg.norm(eye - at))
# Base distance: how far the camera must be for the bbox diagonal
# to fill the frame at the given vertical FOV.
import math
base_distance = diag / (
2.0 * math.tan(math.radians(fov_y / 2.0))
)
# Scale factor maps directly: smaller scale (zoomed in) → closer camera.
adjusted_distance = base_distance * view_scale
# Direction from target toward the original eye position.
direction = eye - at
norm = np.linalg.norm(direction)
if norm < 1e-9:
direction = np.array([0.0, 0.0, 1.0])
else:
direction /= norm
new_eye = at + direction * adjusted_distance
return RenderCamera(
origin=tuple(float(v) for v in new_eye),
target=tuple(float(v) for v in at),
up=tuple(float(v) for v in up),
fov=fov_y,
)
def fit_camera(self, padding: float = 0.05) -> None:
"""Fit camera to show all displayed objects.