GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — Making a Circle Selector · Issue #742 · fastplotlib/fastplotlib Open DescriptionHi I was interesed in making a circle selector. I've been playing around with it a little bit but was interested if you had guidance on a couple of points. Primarily when looking at the Rectangle Selector you are using a 3D object rather than a 2D one? Is there a good reason for this? I think the code that I have (mostly) works although I need to work on the resize and the selection code. Would this be of interest? I can also abstract this to an ellipse if we just make our own custom geometry. One other question is how is the Screen.Recording.2025-03-03.at.8.47.10.AM.movclass CircleSelectionFeature(GraphicFeature): """ **additional event attributes:** +----------------------+----------+------------------------------------+ | attribute | type | description | +======================+==========+====================================+ | get_selected_indices | callable | returns indices under the selector | +----------------------+----------+------------------------------------+ | get_selected_data | callable | returns data under the selector | +----------------------+----------+------------------------------------+ **info dict:** +-------------0--+------------+-------------------------------------------+ | dict key | value type | value description | +===============+============+===========================================+ | value | np.ndarray | new [centerx, centery, radius, inner_radius] of selection | +-------------+------------+-------------------------------------------+ | limits | np.ndarray | new [xmin, xmax, ymin, ymax] for the ind | +------------+------------+-------------------------------------------+ | size_limits| np.ndarray | new [xmin, xmax, ymin, ymax] for the ind | +------------+------------+-------------------------------------------+ """ def __init__( self, value: tuple[float, float, float, float], limits: tuple[float, float, float, float], size_limits: tuple[float, float, float, float]=None, ): super().__init__() self._limits = limits self._value = tuple(int(v) for v in value) self._size_limits = size_limits @property def value(self) -> np.ndarray[float]: """ (centerx, centery, radius, inner_radius) of the selection, in data space """ return self._value def set_value(self, selector, value: Sequence[float]): """ Set the selection of the rectangle selector. Parameters ---------- selector: RectangleSelector value: (float, float, float, float) new values (centerx, centery, radius, inner_radius) of the selection """ if not len(value) == 4: raise TypeError( "Selection must be an array, tuple, list, or sequence in the form of `(xmin, xmax, ymin, ymax)`, " "where `xmin`, `xmax`, `ymin`, `ymax` are numeric values." ) # convert to array value = np.asarray(value, dtype=np.float32) # clip the center values if they are outside of the selection. # the radius should be allowed to move beyond the limits value[0] = value[0].clip(self._limits[0], self._limits[1]) value[1] = value[1].clip(self._limits[2], self._limits[3]) x, y, radius, inner_radius = value # make sure that the radius is greater than 0 and the inner radius is less than the radius if radius <= 0 or inner_radius> radius: return radial_segments = 360 cap = generate_cap(radius=radius, height=1, radial_segments=radial_segments, theta_start=0, theta_length=np.pi * 2) cap_outline = generate_cap(radius=radius, height=1.01, radial_segments=radial_segments, theta_start=0, theta_length=np.pi * 2) positions, normals, texcoords, indices = cap selector.fill.geometry.positions.data[:] = positions + np.array([x, y, 0]) positions, normals, texcoords, indices = cap_outline # shift the circle to the correct position selector.edges[0].geometry.positions.data[:] = positions[1:] + np.array([x, y, 0]) # change the edge lines self._value = value # send changes to GPU selector.fill.geometry.positions.update_range() for edge in selector.edges: edge.geometry.positions.update_range() # send event if len(self._event_handlers) < 1: return event = FeatureEvent("selection", {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data # calls any events self._call_event_handlers(event) class CircleSelector(BaseSelector): def __init__(self, center: Sequence[float], radius: float, limits: Sequence[float], inner_radius: float = 0, parent: Graphic = None, resizable: bool = True, fill_color=(0, 0, 0.35), edge_color=(0.8, 0.6, 0), vertex_color=(0.7, 0.4, 0), edge_thickness: float = 4, vertex_thickness: float = 8, arrow_keys_modifier: str = "Shift", size_limits: Sequence[float] = None, name: str = None,): if not len(center) == 2: raise ValueError() # lots of very close to zero values etc. so round them center = tuple(map(round, center)) radius = round(radius) inner_radius = round(inner_radius) self._parent = parent self._center = np.asarray(center) self._resizable = resizable self._limits = np.asarray(limits) self.size_limits = size_limits center = np.asarray(center) # world object for this will be a group # basic mesh for the fill area of the selector # line for each edge of the selector group = pygfx.Group() self._fill_color = pygfx.Color(fill_color) self._edge_color = pygfx.Color(edge_color) self._vertex_color = pygfx.Color(vertex_color) if radius < 0: raise ValueError() radial_segments = 360 cap = generate_cap(radius=radius, height=1, radial_segments=radial_segments, theta_start=0, theta_length=np.pi * 2) cap_outline = generate_cap(radius=radius, height=1.01, radial_segments=radial_segments, theta_start=0, theta_length=np.pi * 2) positions, normals, texcoords, indices = cap circle_geo = pygfx.Geometry( indices=indices.reshape((-1, 3)), positions=positions, ) self.fill = pygfx.Mesh(circle_geo, pygfx.MeshBasicMaterial( color=pygfx.Color(self.fill_color), pick_write=True ), ) self.fill.world.position = (0, 0, -2) group.add(self.fill) positions, normals, texcoords, indices = cap_outline positions_outline = positions[1:] circle_outline_geo = pygfx.Geometry( indices=indices.reshape((-1, 3))[1:], positions=positions_outline, ) outline = pygfx.Line( geometry= circle_outline_geo, material= pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color) ) self.edges = (outline,) outline.world.z = -0.5 group.add(outline) selection = np.asarray(tuple(center) + (radius, inner_radius)) self._selection = CircleSelectionFeature(selection, limits=self._limits, size_limits=size_limits) # include parent offset if parent is not None: offset = (parent.offset[0], parent.offset[1], 0) else: offset = (0, 0, 0) BaseSelector.__init__( self, edges=self.edges, fill=(self.fill,), hover_responsive=(*self.edges,), arrow_keys_modifier=arrow_keys_modifier, parent=parent, name=name, offset=offset, ) self._set_world_object(group) self.selection = selection def _move_graphic(self, delta: np.ndarray): # new selection positions centerx_new = self.selection[0] + delta[0] centery_new = self.selection[1] + delta[1] # move entire selector if source is fill if self._move_info.source == self.fill: # set thew new bounds self._selection.set_value(self, (centerx_new, centery_new, self.selection[2], self.selection[3])) self.selection = (centerx_new, centery_new, self.selection[2], self.selection[3]) return # if selector not resizable return if not self._resizable: return elif self._move_info.source == self.edges[0]: if delta[0] < 0 or delta[1] < 0: radius_change = -np.sqrt(delta[0]**2 + delta[1]**2) else: radius_change = np.sqrt(delta[0]**2 + delta[1]**2) new_radius = self.selection[2] + radius_change if new_radius < 0: return if self.size_limits is not None: if new_radius < self.size_limits[0] or new_radius > self.size_limits[1]: return values = (self.selection[0], self.selection[1], self.selection[2]+radius_change, self.selection[3]) self._selection.set_value(self, values) self.selection = values return Activity
MetadataMetadataAssigneesLabelsNo labels TypeProjectsMilestoneRelationshipsDevelopmentIssue actions |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |