From 598dd8b2fde21a44035a547b8ae7df28277ce542 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Thu, 1 May 2025 23:29:34 -0400 Subject: [PATCH 1/4] proof of concept --- examples/reference_spaces/line_scales.py | 24 +++++ fastplotlib/graphics/_axes.py | 48 +++++----- fastplotlib/graphics/_base.py | 6 +- fastplotlib/layouts/_graphic_methods_mixin.py | 31 ++++--- fastplotlib/layouts/_plot_area.py | 47 +++++++++- fastplotlib/layouts/_reference_space.py | 90 +++++++++++++++++++ scripts/generate_add_graphic_methods.py | 11 ++- 7 files changed, 212 insertions(+), 45 deletions(-) create mode 100644 examples/reference_spaces/line_scales.py create mode 100644 fastplotlib/layouts/_reference_space.py diff --git a/examples/reference_spaces/line_scales.py b/examples/reference_spaces/line_scales.py new file mode 100644 index 000000000..585e7e276 --- /dev/null +++ b/examples/reference_spaces/line_scales.py @@ -0,0 +1,24 @@ +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 10 * np.pi, 1000) +ys = np.sin(xs) + +ys100 = ys * 1000 + +l1 = np.column_stack([xs, ys]) +l2 = np.column_stack([xs, ys100]) + +fig = fpl.Figure(size=(500, 400)) + +fig[0, 0].add_line(l1) +fig.show(maintain_aspect=False) +fig[0, 0].auto_scale(zoom=0.4) + +rs = fig[0, 0].add_reference_space(scale=(1, 500, 1)) +l2 = fig[0, 0].add_line(l2, reference_space=rs, colors="r") +l2.add_axes(rs) +l2.axes.y.line.material.color = "r" + +fpl.loop.run() diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 10774fc2a..1895406b0 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -144,7 +144,7 @@ def yz(self) -> Grid: class Axes: def __init__( self, - plot_area, + reference_space, intersection: tuple[int, int, int] | None = None, x_kwargs: dict = None, y_kwargs: dict = None, @@ -157,7 +157,7 @@ def __init__( [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] ), ): - self._plot_area = plot_area + self._reference_space = reference_space if x_kwargs is None: x_kwargs = dict() @@ -193,20 +193,20 @@ def __init__( self.x.end_pos = 100, 0, 0 self.x.start_value = self.x.start_pos[0] - offset[0] statsx = self.x.update( - self._plot_area.camera, self._plot_area.viewport.logical_size + self._reference_space.camera, self._reference_space.viewport.logical_size ) self.y.start_pos = 0, 0, 0 self.y.end_pos = 0, 100, 0 self.y.start_value = self.y.start_pos[1] - offset[1] statsy = self.y.update( - self._plot_area.camera, self._plot_area.viewport.logical_size + self._reference_space.camera, self._reference_space.viewport.logical_size ) self.z.start_pos = 0, 0, 0 self.z.end_pos = 0, 0, 100 self.z.start_value = self.z.start_pos[1] - offset[2] - self.z.update(self._plot_area.camera, self._plot_area.viewport.logical_size) + self.z.update(self._reference_space.camera, self._reference_space.viewport.logical_size) # world object for the rulers + grids self._world_object = pygfx.Group() @@ -219,7 +219,7 @@ def __init__( ) # set z ruler invisible for orthographic projections for now - if self._plot_area.camera.fov == 0: + if self._reference_space.camera.fov == 0: # TODO: allow any orientation in the future even for orthographic projections self.z.visible = False @@ -251,7 +251,7 @@ def __init__( self._grids = Grids(**_grids) self.world_object.add(self._grids) - if self._plot_area.camera.fov == 0: + if self._reference_space.camera.fov == 0: # orthographic projection, place grids far away self._grids.local.z = -1000 @@ -382,13 +382,13 @@ def update_using_bbox(self, bbox): """ # flip axes if camera scale is flipped - if self._plot_area.camera.local.scale_x < 0: + if self._reference_space.camera.local.scale_x < 0: bbox[0, 0], bbox[1, 0] = bbox[1, 0], bbox[0, 0] - if self._plot_area.camera.local.scale_y < 0: + if self._reference_space.camera.local.scale_y < 0: bbox[0, 1], bbox[1, 1] = bbox[1, 1], bbox[0, 1] - if self._plot_area.camera.local.scale_z < 0: + if self._reference_space.camera.local.scale_z < 0: bbox[0, 2], bbox[1, 2] = bbox[1, 2], bbox[0, 2] if self.intersection is None: @@ -413,8 +413,8 @@ def update_using_camera(self): if not self.visible: return - if self._plot_area.camera.fov == 0: - xpos, ypos, width, height = self._plot_area.viewport.rect + if self._reference_space.camera.fov == 0: + xpos, ypos, width, height = self._reference_space.viewport.rect # orthographic projection, get ranges using inverse # get range of screen space by getting the corners @@ -442,8 +442,8 @@ def update_using_camera(self): # self.y.local.rotation # ) - min_vals = self._plot_area.map_screen_to_world((xmin, ymin)) - max_vals = self._plot_area.map_screen_to_world((xmax, ymax)) + min_vals = self._reference_space.map_screen_to_world((xmin, ymin)) + max_vals = self._reference_space.map_screen_to_world((xmax, ymax)) if min_vals is None or max_vals is None: return @@ -462,14 +462,14 @@ def update_using_camera(self): else: # set ruler start and end positions based on scene bbox - bbox = self._plot_area._fpl_graphics_scene.get_world_bounding_box() + bbox = self._reference_space._fpl_graphics_scene.get_world_bounding_box() if self.intersection is None: - if self._plot_area.camera.fov == 0: + if self._reference_space.camera.fov == 0: # place the ruler close to the left and bottom edges of the viewport # TODO: determine this for perspective projections xscreen_10, yscreen_10 = xpos + (width * 0.1), ypos + (height * 0.9) - intersection = self._plot_area.map_screen_to_world( + intersection = self._reference_space.map_screen_to_world( (xscreen_10, yscreen_10) ) else: @@ -502,7 +502,7 @@ def update(self, bbox, intersection): world_x_10, world_y_10, world_z_10 = intersection # swap min and max for each dimension if necessary - if self._plot_area.camera.local.scale_y < 0: + if self._reference_space.camera.local.scale_y < 0: world_ymin, world_ymax = world_ymax, world_ymin self.y.tick_side = "right" # swap tick side self.x.tick_side = "right" @@ -510,7 +510,7 @@ def update(self, bbox, intersection): self.y.tick_side = "left" self.x.tick_side = "right" - if self._plot_area.camera.local.scale_x < 0: + if self._reference_space.camera.local.scale_x < 0: world_xmin, world_xmax = world_xmax, world_xmin self.x.tick_side = "left" @@ -519,7 +519,7 @@ def update(self, bbox, intersection): self.x.start_value = self.x.start_pos[0] - self.offset[0] statsx = self.x.update( - self._plot_area.camera, self._plot_area.viewport.logical_size + self._reference_space.camera, self._reference_space.viewport.logical_size ) self.y.start_pos = world_x_10, world_ymin, world_z_10 @@ -527,16 +527,16 @@ def update(self, bbox, intersection): self.y.start_value = self.y.start_pos[1] - self.offset[1] statsy = self.y.update( - self._plot_area.camera, self._plot_area.viewport.logical_size + self._reference_space.camera, self._reference_space.viewport.logical_size ) - if self._plot_area.camera.fov != 0: + if self._reference_space.camera.fov != 0: self.z.start_pos = world_x_10, world_y_10, world_zmin self.z.end_pos = world_x_10, world_y_10, world_zmax self.z.start_value = self.z.start_pos[2] - self.offset[2] statsz = self.z.update( - self._plot_area.camera, self._plot_area.viewport.logical_size + self._reference_space.camera, self._reference_space.viewport.logical_size ) major_step_z = statsz["tick_step"] @@ -546,7 +546,7 @@ def update(self, bbox, intersection): self.grids.xy.major_step = major_step_x, major_step_y self.grids.xy.minor_step = 0.2 * major_step_x, 0.2 * major_step_y - if self._plot_area.camera.fov != 0: + if self._reference_space.camera.fov != 0: self.grids.xz.major_step = major_step_x, major_step_z self.grids.xz.minor_step = 0.2 * major_step_x, 0.2 * major_step_z diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index e115107b0..168f976c8 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -447,15 +447,15 @@ def rotate(self, alpha: float, axis: Literal["x", "y", "z"] = "y"): def axes(self) -> Axes: return self._axes - def add_axes(self): + def add_axes(self, reference_frame): """Add axes onto this Graphic""" if self._axes is not None: raise AttributeError("Axes already added onto this graphic") - self._axes = Axes(self._plot_area, offset=self.offset, grids=False) + self._axes = Axes(reference_frame, offset=self.offset, grids=False) self._axes.world_object.local.rotation = self.world_object.local.rotation - self._plot_area.scene.add(self.axes.world_object) + reference_frame.scene.add(self.axes.world_object) self._axes.update_using_bbox(self.world_object.get_world_bounding_box()) @property diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a753eec73..d6eedeee5 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -15,11 +15,16 @@ def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic: else: center = False + if "reference_space" in kwargs.keys(): + reference_space = kwargs.pop("reference_space") + else: + reference_space = 0 + if "name" in kwargs.keys(): self._check_graphic_name_exists(kwargs["name"]) graphic = graphic_class(*args, **kwargs) - self.add_graphic(graphic, center=center) + self.add_graphic(graphic, center=center, reference_space=reference_space) return graphic @@ -32,7 +37,7 @@ def add_image( interpolation: str = "nearest", cmap_interpolation: str = "linear", isolated_buffer: bool = True, - **kwargs, + **kwargs ) -> ImageGraphic: """ @@ -78,7 +83,7 @@ def add_image( interpolation, cmap_interpolation, isolated_buffer, - **kwargs, + **kwargs ) def add_line_collection( @@ -96,7 +101,7 @@ def add_line_collection( metadatas: Union[Sequence[Any], numpy.ndarray] = None, isolated_buffer: bool = True, kwargs_lines: list[dict] = None, - **kwargs, + **kwargs ) -> LineCollection: """ @@ -169,7 +174,7 @@ def add_line_collection( metadatas, isolated_buffer, kwargs_lines, - **kwargs, + **kwargs ) def add_line( @@ -183,7 +188,7 @@ def add_line( cmap_transform: Union[numpy.ndarray, Iterable] = None, isolated_buffer: bool = True, size_space: str = "screen", - **kwargs, + **kwargs ) -> LineGraphic: """ @@ -234,7 +239,7 @@ def add_line( cmap_transform, isolated_buffer, size_space, - **kwargs, + **kwargs ) def add_line_stack( @@ -253,7 +258,7 @@ def add_line_stack( separation: float = 10.0, separation_axis: str = "y", kwargs_lines: list[dict] = None, - **kwargs, + **kwargs ) -> LineStack: """ @@ -334,7 +339,7 @@ def add_line_stack( separation, separation_axis, kwargs_lines, - **kwargs, + **kwargs ) def add_scatter( @@ -349,7 +354,7 @@ def add_scatter( sizes: Union[float, numpy.ndarray, Iterable[float]] = 1, uniform_size: bool = False, size_space: str = "screen", - **kwargs, + **kwargs ) -> ScatterGraphic: """ @@ -409,7 +414,7 @@ def add_scatter( sizes, uniform_size, size_space, - **kwargs, + **kwargs ) def add_text( @@ -422,7 +427,7 @@ def add_text( screen_space: bool = True, offset: tuple[float] = (0, 0, 0), anchor: str = "middle-center", - **kwargs, + **kwargs ) -> TextGraphic: """ @@ -473,5 +478,5 @@ def add_text( screen_space, offset, anchor, - **kwargs, + **kwargs ) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 2934e0589..1a25fd18b 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -8,8 +8,9 @@ from pylinalg import vec_transform, vec_unproject from rendercanvas import BaseRenderCanvas +from ._reference_space import ReferenceSpace from ._utils import create_controller -from ..graphics._base import Graphic +from ..graphics import Graphic from ..graphics.selectors._base_selector import BaseSelector from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend @@ -115,6 +116,8 @@ def __init__( self._background = pygfx.Background(None, self._background_material) self.scene.add(self._background) + self._reference_spaces: list[ReferenceSpace] = list() + def get_figure(self, obj=None): """Get Figure instance that contains this plot area""" if obj is None: @@ -272,6 +275,35 @@ def background_color(self, colors: str | tuple[float]): """1, 2, or 4 colors, each color must be acceptable by pygfx.Color""" self._background_material.set_colors(*colors) + @property + def reference_spaces(self) -> tuple[ReferenceSpace, ...]: + return tuple(self._reference_spaces) + + def add_reference_space( + self, + position: tuple[float, float, float] = (0., 0., 0.), + scale: tuple[float, float, float] = (1., 1., 1.), + name: str | None = None + ) -> ReferenceSpace: + camera = pygfx.PerspectiveCamera() + + state = self.camera.get_state() + camera.set_state(state) + + # camera.world.position = position + camera.world.scale = scale + camera.maintain_aspect = False + + scene = pygfx.Scene() + + controller = pygfx.PanZoomController(camera) + controller.register_events(self.viewport) + + reference_space = ReferenceSpace(scene, camera, controller, self.viewport, name) + self._reference_spaces.append(reference_space) + + return reference_space + def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False ) -> np.ndarray | None: @@ -314,6 +346,9 @@ def _render(self): # does not flush, flush must be implemented in user-facing Plot objects self.viewport.render(self.scene, self.camera) + for reference_space in self.reference_spaces: + self.viewport.render(reference_space.scene, reference_space.camera) + for child in self.children: child._render() @@ -393,7 +428,7 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) - def add_graphic(self, graphic: Graphic, center: bool = True): + def add_graphic(self, graphic: Graphic, center: bool = True, reference_space: ReferenceSpace | int | str = 0): """ Add a Graphic to the scene @@ -413,7 +448,7 @@ def add_graphic(self, graphic: Graphic, center: bool = True): self._fpl_graphics_scene.add(graphic.world_object) return - self._add_or_insert_graphic(graphic=graphic, center=center, action="add") + self._add_or_insert_graphic(graphic=graphic, center=center, action="add", reference_space=reference_space) if self.camera.fov == 0: # for orthographic positions stack objects along the z-axis @@ -469,6 +504,7 @@ def _add_or_insert_graphic( center: bool = True, action: str = Literal["insert", "add"], index: int = 0, + reference_space: ReferenceSpace | str | int = 0, ): """Private method to handle inserting or adding a graphic to a PlotArea.""" if not isinstance(graphic, Graphic): @@ -489,7 +525,10 @@ def _add_or_insert_graphic( elif isinstance(graphic, Graphic): obj_list = self._graphics - self._fpl_graphics_scene.add(graphic.world_object) + if isinstance(reference_space, ReferenceSpace): + reference_space.scene.add(graphic.world_object) + else: + self._fpl_graphics_scene.add(graphic.world_object) else: raise TypeError("graphic must be of type Graphic | BaseSelector | Legend") diff --git a/fastplotlib/layouts/_reference_space.py b/fastplotlib/layouts/_reference_space.py new file mode 100644 index 000000000..7b7da609d --- /dev/null +++ b/fastplotlib/layouts/_reference_space.py @@ -0,0 +1,90 @@ +import numpy as np + +import pygfx +from pylinalg import vec_transform, vec_unproject + +from ..graphics import Graphic + + +class ReferenceSpace: + def __init__( + self, + scene: pygfx.Scene, + camera: pygfx.Camera, + controller: pygfx.Controller, + viewport: pygfx.Viewport, + name: str | None = None, + ): + self._scene = scene + self._camera = camera + self._controller = controller + self.viewport = viewport + self._name = name + + self._graphics: list[Graphic] = list() + + @property + def name(self) -> str: + return self._name + + @property + def scene(self) -> pygfx.Scene: + return self._scene + + @property + def camera(self) -> pygfx.Camera: + return self._camera + + # @property + # def controller(self): + # same controller for all reference spaces in one PlotArea I think? + # Use key events to add or remove a camera from the PlotArea dynamically? + # pass + + def auto_scale(self): + pass + + def center(self): + pass + + @property + def graphics(self) -> np.ndarray[Graphic]: + graphics = np.asarray(self._graphics) + graphics.flags.writeable = False + return graphics + + def map_screen_to_world( + self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False + ) -> np.ndarray | None: + """ + Map screen position to world position + + Parameters + ---------- + pos: (float, float) | pygfx.PointerEvent + ``(x, y)`` screen coordinates, or ``pygfx.PointerEvent`` + + """ + if isinstance(pos, pygfx.PointerEvent): + pos = pos.x, pos.y + + if not allow_outside and not self.viewport.is_inside(*pos): + return None + + vs = self.viewport.logical_size + + # get position relative to viewport + pos_rel = ( + pos[0] - self.viewport.rect[0], + pos[1] - self.viewport.rect[1], + ) + + # convert screen position to NDC + pos_ndc = (pos_rel[0] / vs[0] * 2 - 1, -(pos_rel[1] / vs[1] * 2 - 1), 0) + + # get world position + pos_ndc += vec_transform(self.camera.world.position, self.camera.camera_matrix) + pos_world = vec_unproject(pos_ndc[:2], self.camera.camera_matrix) + + # default z is zero for now + return np.array([*pos_world[:2], 0]) diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index 533ae77c6..1080d99da 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -19,6 +19,9 @@ for name, obj in inspect.getmembers(graphics): if inspect.isclass(obj): + if obj.__name__ == "Graphic": + # skip base class + continue modules.append(obj) @@ -42,10 +45,16 @@ def generate_add_graphics_methods(): f.write(" center = kwargs.pop('center')\n") f.write(" else:\n") f.write(" center = False\n\n") + + f.write(" if 'reference_space' in kwargs.keys():\n") + f.write(" reference_space = kwargs.pop('reference_space')\n") + f.write(" else:\n") + f.write(" reference_space = 0\n\n") + f.write(" if 'name' in kwargs.keys():\n") f.write(" self._check_graphic_name_exists(kwargs['name'])\n\n") f.write(" graphic = graphic_class(*args, **kwargs)\n") - f.write(" self.add_graphic(graphic, center=center)\n\n") + f.write(" self.add_graphic(graphic, center=center, reference_space=reference_space)\n\n") f.write(" return graphic\n\n") for m in modules: From 4599fa74397e7614cb8a1225bdc1fa27836f4107 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Thu, 1 May 2025 23:43:41 -0400 Subject: [PATCH 2/4] controller property --- fastplotlib/layouts/_reference_space.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fastplotlib/layouts/_reference_space.py b/fastplotlib/layouts/_reference_space.py index 7b7da609d..79c396af9 100644 --- a/fastplotlib/layouts/_reference_space.py +++ b/fastplotlib/layouts/_reference_space.py @@ -35,11 +35,9 @@ def scene(self) -> pygfx.Scene: def camera(self) -> pygfx.Camera: return self._camera - # @property - # def controller(self): - # same controller for all reference spaces in one PlotArea I think? - # Use key events to add or remove a camera from the PlotArea dynamically? - # pass + @property + def controller(self) -> pygfx.Controller: + return self._controller def auto_scale(self): pass From a1934d5ab98649a4e6eeee019990d20b0f3862e8 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 7 May 2025 21:46:50 -0400 Subject: [PATCH 3/4] rename --- fastplotlib/layouts/_plot_area.py | 49 ++++++++++++++----------- fastplotlib/layouts/_reference_space.py | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 1a25fd18b..f3a791117 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -8,7 +8,7 @@ from pylinalg import vec_transform, vec_unproject from rendercanvas import BaseRenderCanvas -from ._reference_space import ReferenceSpace +from ._reference_space import ReferenceFrame from ._utils import create_controller from ..graphics import Graphic from ..graphics.selectors._base_selector import BaseSelector @@ -116,7 +116,7 @@ def __init__( self._background = pygfx.Background(None, self._background_material) self.scene.add(self._background) - self._reference_spaces: list[ReferenceSpace] = list() + self._reference_frames: list[ReferenceFrame] = list() def get_figure(self, obj=None): """Get Figure instance that contains this plot area""" @@ -276,33 +276,40 @@ def background_color(self, colors: str | tuple[float]): self._background_material.set_colors(*colors) @property - def reference_spaces(self) -> tuple[ReferenceSpace, ...]: - return tuple(self._reference_spaces) + def reference_frames(self) -> tuple[ReferenceFrame, ...]: + return tuple(self._reference_frames) - def add_reference_space( + def add_reference_frame( self, - position: tuple[float, float, float] = (0., 0., 0.), - scale: tuple[float, float, float] = (1., 1., 1.), + position: tuple[float, float, float] | None = None, + scale: tuple[float, float, float] | None = None, + controller_type: str = None, + controller_include_state=None, + controller_exclude_state=None, name: str | None = None - ) -> ReferenceSpace: + ) -> ReferenceFrame: camera = pygfx.PerspectiveCamera() state = self.camera.get_state() camera.set_state(state) - # camera.world.position = position - camera.world.scale = scale - camera.maintain_aspect = False + if position is not None: + camera.world.position = position + if scale is not None: + camera.world.scale = scale + + camera.maintain_aspect = self.camera.maintain_aspect scene = pygfx.Scene() - controller = pygfx.PanZoomController(camera) + controller = pygfx.PanZoomController() + controller.add_camera(camera, include_state=controller_include_state, exclude_state=controller_exclude_state) controller.register_events(self.viewport) - reference_space = ReferenceSpace(scene, camera, controller, self.viewport, name) - self._reference_spaces.append(reference_space) + ref_frame = ReferenceFrame(scene, camera, controller, self.viewport, name) + self._reference_frames.append(ref_frame) - return reference_space + return ref_frame def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False @@ -346,7 +353,7 @@ def _render(self): # does not flush, flush must be implemented in user-facing Plot objects self.viewport.render(self.scene, self.camera) - for reference_space in self.reference_spaces: + for reference_space in self.reference_frames: self.viewport.render(reference_space.scene, reference_space.camera) for child in self.children: @@ -428,7 +435,7 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) - def add_graphic(self, graphic: Graphic, center: bool = True, reference_space: ReferenceSpace | int | str = 0): + def add_graphic(self, graphic: Graphic, center: bool = True, reference_frame: ReferenceFrame | int | str = 0): """ Add a Graphic to the scene @@ -448,7 +455,7 @@ def add_graphic(self, graphic: Graphic, center: bool = True, reference_space: Re self._fpl_graphics_scene.add(graphic.world_object) return - self._add_or_insert_graphic(graphic=graphic, center=center, action="add", reference_space=reference_space) + self._add_or_insert_graphic(graphic=graphic, center=center, action="add", reference_frame=reference_frame) if self.camera.fov == 0: # for orthographic positions stack objects along the z-axis @@ -504,7 +511,7 @@ def _add_or_insert_graphic( center: bool = True, action: str = Literal["insert", "add"], index: int = 0, - reference_space: ReferenceSpace | str | int = 0, + reference_frame: ReferenceFrame | str | int = 0, ): """Private method to handle inserting or adding a graphic to a PlotArea.""" if not isinstance(graphic, Graphic): @@ -525,8 +532,8 @@ def _add_or_insert_graphic( elif isinstance(graphic, Graphic): obj_list = self._graphics - if isinstance(reference_space, ReferenceSpace): - reference_space.scene.add(graphic.world_object) + if isinstance(reference_frame, ReferenceFrame): + reference_frame.scene.add(graphic.world_object) else: self._fpl_graphics_scene.add(graphic.world_object) diff --git a/fastplotlib/layouts/_reference_space.py b/fastplotlib/layouts/_reference_space.py index 79c396af9..930769dd6 100644 --- a/fastplotlib/layouts/_reference_space.py +++ b/fastplotlib/layouts/_reference_space.py @@ -6,7 +6,7 @@ from ..graphics import Graphic -class ReferenceSpace: +class ReferenceFrame: def __init__( self, scene: pygfx.Scene, From 9bfd8164af9cc82b97385bbc524d2ad20b530ff3 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 7 May 2025 21:47:57 -0400 Subject: [PATCH 4/4] example --- examples/reference_spaces/line_scales.py | 41 ++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/examples/reference_spaces/line_scales.py b/examples/reference_spaces/line_scales.py index 585e7e276..5d07e28cd 100644 --- a/examples/reference_spaces/line_scales.py +++ b/examples/reference_spaces/line_scales.py @@ -1,6 +1,8 @@ import numpy as np import fastplotlib as fpl - +from fastplotlib.ui import DebugWindow +import pygfx +from icecream import ic xs = np.linspace(0, 10 * np.pi, 1000) ys = np.sin(xs) @@ -16,9 +18,42 @@ fig.show(maintain_aspect=False) fig[0, 0].auto_scale(zoom=0.4) -rs = fig[0, 0].add_reference_space(scale=(1, 500, 1)) +rs = fig[0, 0].add_reference_frame( + scale=(1, 500, 1), +) l2 = fig[0, 0].add_line(l2, reference_space=rs, colors="r") l2.add_axes(rs) -l2.axes.y.line.material.color = "r" +l2.axes.y.line.material.color = "m" + + +@fig.renderer.add_event_handler("key_down") +def change_y_scale(ev: pygfx.KeyboardEvent): + if ev.key != "1": + return + + rs.controller.remove_camera(rs.camera) + rs.controller.add_camera(rs.camera, include_state={"height"}) + + fig[0, 0].controller.enabled = False + + +@fig.renderer.add_event_handler("key_down") +def change_y_scale(ev: pygfx.KeyboardEvent): + if ev.key != "0": + return + + rs.controller.remove_camera(rs.camera) + rs.controller.add_camera(rs.camera) + fig[0, 0].controller.enabled = True + + +debug_objs = [ + fig[0, 0].camera.get_state, + rs.camera.get_state +] + +debug_window = DebugWindow(debug_objs) +fig.add_gui(debug_window) + fpl.loop.run() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy