Skip to content

Commit

Permalink
Adding Geodesic Keypoint Erasing (#11)
Browse files Browse the repository at this point in the history
* Adding point deletion support

* Bumping up version plus adding comments
  • Loading branch information
UncleFirefox authored Mar 17, 2021
1 parent 153f8c1 commit 4e6f1d1
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 54 deletions.
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Albert Rodriguez",
"description": "Tools to take measures for Avatar",
"blender": (2, 92, 0),
"version": (0, 4, 1),
"version": (0, 4, 5),
"location": "View3D > Toolshelf",
"warning": "This plugin is only compatible with Blender 2.92",
"category": "Add measures",
Expand Down
155 changes: 106 additions & 49 deletions addon/operator/geopath_datastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def __init__(self, context, selected_obj):

self.epsilon = .0000001
self.max_iters = 100000
self.distance_threshold = 0.006

# geos, fixed, close, far
self.geo_data = [None, None]
Expand Down Expand Up @@ -111,53 +112,6 @@ def grab_mouse_move(self, context, x, y):
# Finally move the key_point
self.key_points[point_pos] = (hit_loc, hit_face)

def redo_geodesic_segment(self, segment_pos, start_loc,
start_face, end_loc, end_face,
cache_pos):

# Special case handling for weird algorithm behavior
should_reverse = cache_pos != 1

# Try using the cached structure before relaunching
# a new geodesic walk
cached_path = self.try_continue_geodesic_walk(
cache_pos, end_loc, end_face)

if cached_path:
self.cleanup_path(start_loc, end_loc, cached_path, should_reverse)
self.path_segments[segment_pos] = cached_path
return

geos, fixed, close, far = geodesic_walk(
self.bme.verts, start_face, start_loc,
end_face, self.max_iters)

path_elements, path = gradient_descent(
geos, end_face, end_loc, self.epsilon)

self.geo_data[cache_pos] = (geos, fixed, close, far)

self.cleanup_path(start_loc, end_loc, path, should_reverse)
self.path_segments[segment_pos] = path

def try_continue_geodesic_walk(self, cache_pos, hit_loc, hit_face):

# Data was not cached
if self.geo_data[cache_pos] is None:
return None

geos, fixed, close, far = self.geo_data[cache_pos]

if not all([v in fixed for v in hit_face.verts]):
continue_geodesic_walk(
geos, fixed, close, far,
hit_face, self.max_iters)

path_elements, path = gradient_descent(
geos, hit_face, hit_loc, self.epsilon)

return path

def grab_start(self):

if (self.hover_point_index is None):
Expand Down Expand Up @@ -232,6 +186,107 @@ def grab_finish(self):

return

def erase_mouse_move(self, context, x, y):

hit, hit_loc, face_ind = self.raycast(context, x, y)

if not hit:
return

# look for keypoints to hover
self.find_keypoint_hover(hit_loc)

def erase_point(self):

if (self.hover_point_index is None):
return

point_pos = self.hover_point_index

# Reset hovering point
self.hover_point_index = None

# I have a segment before point
segment_before = None
if point_pos > 0:
segment_before = self.path_segments[point_pos-1]

# I have a segment after point
segment_after = None
if point_pos < len(self.key_points)-1:
segment_after = self.path_segments[point_pos]

if segment_before:
self.path_segments.remove(segment_before)
if segment_after:
self.path_segments.remove(segment_after)

# Remove position from keypoints
self.key_points.pop(point_pos)

# Redo geodesic path if needed
if segment_before and segment_after:
start_loc, start_face = self.key_points[point_pos-1]
end_loc, end_face = self.key_points[point_pos]
# Recreate the position
self.path_segments.insert(point_pos-1, [])
self.redo_geodesic_segment(
point_pos-1, start_loc, start_face, end_loc, end_face, 0)

# Avoid having garbage in the geo cache
self.geo_data[0] = None

def erase_cancel(self):
# Reset hovering point
self.hover_point_index = None

def redo_geodesic_segment(self, segment_pos, start_loc,
start_face, end_loc, end_face,
cache_pos):

# Special case handling for weird algorithm behavior
should_reverse = cache_pos != 1

# Try using the cached structure before relaunching
# a new geodesic walk
cached_path = self.try_continue_geodesic_walk(
cache_pos, end_loc, end_face)

if cached_path:
self.cleanup_path(start_loc, end_loc, cached_path, should_reverse)
self.path_segments[segment_pos] = cached_path
return

geos, fixed, close, far = geodesic_walk(
self.bme.verts, start_face, start_loc,
end_face, self.max_iters)

path_elements, path = gradient_descent(
geos, end_face, end_loc, self.epsilon)

self.geo_data[cache_pos] = (geos, fixed, close, far)

self.cleanup_path(start_loc, end_loc, path, should_reverse)
self.path_segments[segment_pos] = path

def try_continue_geodesic_walk(self, cache_pos, hit_loc, hit_face):

# Data was not cached
if self.geo_data[cache_pos] is None:
return None

geos, fixed, close, far = self.geo_data[cache_pos]

if not all([v in fixed for v in hit_face.verts]):
continue_geodesic_walk(
geos, fixed, close, far,
hit_face, self.max_iters)

path_elements, path = gradient_descent(
geos, hit_face, hit_loc, self.epsilon)

return path

def draw(self, context, plugin_state):

mx = self.selected_obj.matrix_world
Expand All @@ -252,7 +307,7 @@ def draw(self, context, plugin_state):
self.point_size,
self.point_select_color)

if plugin_state == Geodesic_State.GRAB:
if plugin_state in {Geodesic_State.GRAB, Geodesic_State.ERASE}:
draw.draw_3d_circles(context, points,
self.circle_radius, self.point_color)
if point_highlight_idx is not None:
Expand Down Expand Up @@ -311,7 +366,8 @@ def find_keypoint_hover(self, point):
key_points = [key_point for (key_point, key_face) in self.key_points]

selected_keypoints = list(
filter(lambda x: (x-point).length <= 0.006, key_points)
filter(lambda x: (x-point).length <= self.distance_threshold,
key_points)
)

if selected_keypoints:
Expand All @@ -322,3 +378,4 @@ def find_keypoint_hover(self, point):
class Geodesic_State(Enum):
MAIN = 1
GRAB = 2
ERASE = 3
41 changes: 37 additions & 4 deletions addon/operator/measures_geodesic_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def modal(self, context, event):
return self.handle_main(context, event)
elif self.state == Geodesic_State.GRAB:
return self.handle_grab(context, event)
elif self.state == Geodesic_State.ERASE:
return self.handle_erase(context, event)

return {"RUNNING_MODAL"} # Should not get here but you never know

Expand All @@ -66,6 +68,10 @@ def handle_main(self, context, event):
elif event.type == 'G' and event.value == 'PRESS':
self.state = Geodesic_State.GRAB # Do grab mode

elif event.type == 'E' and event.value == 'PRESS':
context.window.cursor_set("ERASER")
self.state = Geodesic_State.ERASE # Do erase mode

# Adding points
elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
x, y = (event.mouse_region_x, event.mouse_region_y)
Expand Down Expand Up @@ -94,15 +100,16 @@ def handle_grab(self, context, event):
}:
return {'PASS_THROUGH'}

if event.type == 'MOUSEMOVE':
elif event.type == 'MOUSEMOVE':
x, y = (event.mouse_region_x, event.mouse_region_y)
self.geopath.grab_mouse_move(context, x, y)

# try to see if we are grabbing
if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
self.geopath.grab_start()

if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
# stop grabbing when releasing
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
self.geopath.grab_finish()

# cancel grabbing
Expand All @@ -115,6 +122,33 @@ def handle_grab(self, context, event):
context.area.tag_redraw()
return {'RUNNING_MODAL'}

def handle_erase(self, context, event):

# Free navigation
if event.type in {
'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
'WHEELINMOUSE', 'WHEELOUTMOUSE'
}:
return {'PASS_THROUGH'}

elif event.type == 'MOUSEMOVE':
x, y = (event.mouse_region_x, event.mouse_region_y)
self.geopath.erase_mouse_move(context, x, y)

elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
self.geopath.erase_point()

# cancel erasing
elif (event.type in {'RIGHTMOUSE', 'ESC', 'E'}
and event.value == 'PRESS'):

self.geopath.erase_cancel()
context.window.cursor_set("DEFAULT")
self.state = Geodesic_State.MAIN

context.area.tag_redraw()
return {'RUNNING_MODAL'}

def detect_collision(self, context, event):
if event.type == 'MOUSEMOVE':
self.hit_point = None
Expand Down Expand Up @@ -142,7 +176,6 @@ def execute(self, context):

for i in range(1, len(path)):
edges.append(bm.edges.new((vertices[i-1], vertices[i])))
# print("adding {:3f} - {:3f}".format(i-1, i))

me = bpy.data.meshes.new("GeodesicPath")
bm.to_mesh(me)
Expand Down

0 comments on commit 4e6f1d1

Please sign in to comment.