Skip to content

Implement PolygonSelector #837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Implement PolygonSelector #837

wants to merge 14 commits into from

Conversation

almarklein
Copy link
Collaborator

@almarklein almarklein commented May 26, 2025

  • Get it to draw a polygon.
  • Triangulation.
  • Selection.
  • Decide on interaction model.
image

@ivoflipse
Copy link

Next up, add morphing 😎

@almarklein
Copy link
Collaborator Author

add morphing

That's worthy of a separate lib :)

@ivoflipse
Copy link

You already have gfxmorph 🤣

@almarklein
Copy link
Collaborator Author

This is nearly done, but the ux needs some thought and work. I'm thinking something like this:

  • User clicks points to create the polygon. Optionally start with existing polygon.
  • Once created, vertices can be dragged.
  • Clicking on an edge will split it in two, creating an extra vertex.
  • Double-clicking on inside the polygon will delete and let the user create a new polygon.

@kushalkolar
Copy link
Member

kushalkolar commented Jun 29, 2025

This is nearly done, but the ux needs some thought and work. I'm thinking something like this:

  • User clicks points to create the polygon. Optionally start with existing polygon.
  • Once created, vertices can be dragged.
  • Clicking on an edge will split it in two, creating an extra vertex.

All this sounds good to me!

  • Double-clicking on inside the polygon will delete and let the user create a new polygon.

I think this should be explicitly called, or via imgui right click menu. Or clicking on the fill area selects the polygon and presing the "Del" key deletes it.

@almarklein
Copy link
Collaborator Author

almarklein commented Jul 4, 2025

Interaction done! Slightly different than I said earlier:

  • Can initialize the tool with an initial selection.
  • If the initial selection is empty, enter polygon creation mode.
  • Can also call start_new_polygon() to clear the current polygon and draw a new one.
  • When creating a new polygon, clicking adds a vertex. Clicking on the first vertex again will finish the polygon.
  • When created, the vertices can be dragged. When dragged on top of a neighbouring polygon, it will be deleted.
  • When clicking/dragging on a line, a new vertex is inserted.

edit: in the above, a snapping visualization is applied to help the user understand that the vertex will be "merged" with the vertex under the mouse.

@almarklein almarklein marked this pull request as ready for review July 4, 2025 12:41
@almarklein
Copy link
Collaborator Author

I just learned about bermuda, a triangulation lib implemented in Rust, a spin-off of the Napari project. We could try and see if it helps here. It would mean an extra dependency, so maybe not use it unless we need it. But we can at least do some quick benchmarks and add a note, so that in case we do need more speed, we know how to get it.

Comment on lines +417 to +418
# TODO: Update the fill mesh
# selector.fill.geometry.positions = ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the fill mesh is updated here below?

Parameters
----------
selection: List of positions, optional
initial points for the polygon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add to docstring: If None, start clicking to create the selection

Comment on lines +309 to +310
selection: (float, float, float, float), optional
initial (xmin, xmax, ymin, ymax) of the selection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can update by copying pasting the changes from the image docstring


mode: str
index: int
snap_index: int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment for what this is

@property
def selection(self) -> np.ndarray[float]:
"""
The polygon as an array of 3D points.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add to docstring: shape is [n_points, 2] (I'm assuming)

Comment on lines +84 to +85
positions=np.zeros((8, 3), np.float32),
indices=np.zeros((8, 3), np.int32),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you comment why this initialization shape

Comment on lines +87 to +88
self.geometry.positions.draw_range = 0, 0
self.geometry.indices.draw_range = 0, 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do the draw range do?

self.geometry,
pygfx.PointsMaterial(size=vertex_size, color=vertex_color, pick_write=True),
)
self._points.local.z = 0.01 # move it slightly towards the camera
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do this often! Is 0.01 the best way to do it? I usually do 0.5

Comment on lines +380 to +390
elif self._move_info.mode is None:
# Maybe initiate a drag
if ev.target is self._points:
index = ev.pick_info["vertex_index"]
self._start_move_mode("drag", index)
elif ev.target is self._line:
index = ev.pick_info["vertex_index"]
if ev.pick_info["segment_coord"] > 0:
index += 1
self._insert_polygon_vertex(index, world_pos)
self._start_move_mode("drag", index)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the target is the fill area, move the entire polygon?


for handler, event in handlers.items():
self._plot_area.renderer.remove_event_handler(handler, event)
# Are we close to a point that we can snap to?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what exactly is the snapping 🤔

@kushalkolar
Copy link
Member

Sorry for the delay in reviewing this, awesome work thanks a lot! 🥳 🥳 . Some minor comments and questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants