- added renderer
This commit is contained in:
@@ -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 (10–120 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user