Files
test/scripts/startup/bl_operators/bmesh/find_adjacent.py
Sergey Sharybin 03806d0b67 Re-design of submodules used in blender.git
This commit implements described in the #104573.

The goal is to fix the confusion of the submodule hashes change, which are not
ideal for any of the supported git-module configuration (they are either always
visible causing confusion, or silently staged and committed, also causing
confusion).

This commit replaces submodules with a checkout of addons and addons_contrib,
covered by the .gitignore, and locale and developer tools are moved to the
main repository.

This also changes the paths:
- /release/scripts are moved to the /scripts
- /source/tools are moved to the /tools
- /release/datafiles/locale is moved to /locale

This is done to avoid conflicts when using bisect, and also allow buildbot to
automatically "recover" wgen building older or newer branches/patches.

Running `make update` will initialize the local checkout to the changed
repository configuration.

Another aspect of the change is that the make update will support Github style
of remote organization (origin remote pointing to thy fork, upstream remote
pointing to the upstream blender/blender.git).

Pull Request #104755
2023-02-21 16:39:58 +01:00

336 lines
10 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
# Utilities to detect the next matching element (vert/edge/face)
# based on an existing pair of elements.
import bmesh
__all__ = (
"select_prev",
"select_next",
)
def other_edges_over_face(e):
# Can yield same edge multiple times, its fine.
for l in e.link_loops:
yield l.link_loop_next.edge
yield l.link_loop_prev.edge
def other_edges_over_edge(e):
# Can yield same edge multiple times, its fine.
for v in e.verts:
for e_other in v.link_edges:
if e_other is not e:
if not e.is_wire:
yield e_other
def verts_from_elem(ele):
ele_type = type(ele)
if ele_type is bmesh.types.BMFace:
return [l.vert for l in ele.loops]
elif ele_type is bmesh.types.BMEdge:
return [v for v in ele.verts]
elif ele_type is bmesh.types.BMVert:
return [ele]
else:
raise TypeError("wrong type")
def edges_from_elem(ele):
ele_type = type(ele)
if ele_type is bmesh.types.BMFace:
return [l.edge for l in ele.loops]
elif ele_type is bmesh.types.BMEdge:
return [ele]
elif ele_type is bmesh.types.BMVert:
return [e for e in ele.link_edges]
else:
raise TypeError("wrong type")
def elems_depth_search(ele_init, depths, other_edges_over_cb, results_init=None):
"""
List of depths -> List of elems that match those depths.
"""
depth_max = max(depths)
depth_min = min(depths)
depths_sorted = tuple(sorted(depths))
stack_old = edges_from_elem(ele_init)
stack_new = []
stack_visit = set(stack_old)
vert_depths = {}
vert_depths_setdefault = vert_depths.setdefault
depth = 0
while stack_old and depth <= depth_max:
for ele in stack_old:
for v in verts_from_elem(ele):
vert_depths_setdefault(v, depth)
for ele_other in other_edges_over_cb(ele):
stack_visit_len = len(stack_visit)
stack_visit.add(ele_other)
if stack_visit_len != len(stack_visit):
stack_new.append(ele_other)
stack_new, stack_old = stack_old, stack_new
stack_new[:] = []
depth += 1
# now we have many verts in vert_depths which are attached to elements
# which are candidates for matching with depths
if type(ele_init) is bmesh.types.BMFace:
test_ele = {
l.face for v, depth in vert_depths.items()
if depth >= depth_min for l in v.link_loops}
elif type(ele_init) is bmesh.types.BMEdge:
test_ele = {
e for v, depth in vert_depths.items()
if depth >= depth_min for e in v.link_edges if not e.is_wire}
else:
test_ele = {
v for v, depth in vert_depths.items()
if depth >= depth_min}
result_ele = set()
vert_depths_get = vert_depths.get
# re-used each time, will always be the same length
depths_test = [None] * len(depths)
for ele in test_ele:
verts_test = verts_from_elem(ele)
if len(verts_test) != len(depths):
continue
if results_init is not None and ele not in results_init:
continue
if ele in result_ele:
continue
ok = True
for i, v in enumerate(verts_test):
depth = vert_depths_get(v)
if depth is not None:
depths_test[i] = depth
else:
ok = False
break
if ok:
if depths_sorted == tuple(sorted(depths_test)):
# Note, its possible the order of sorted items moves the values out-of-order.
# for this we could do a circular list comparison,
# however - this is such a rare case that we're ignoring it.
result_ele.add(ele)
return result_ele
def elems_depth_measure(ele_dst, ele_src, other_edges_over_cb):
"""
Returns·ele_dst vert depths from ele_src, aligned with ele_dst verts.
"""
stack_old = edges_from_elem(ele_src)
stack_new = []
stack_visit = set(stack_old)
# continue until we've reached all verts in the destination
ele_dst_verts = verts_from_elem(ele_dst)
all_dst = set(ele_dst_verts)
all_dst_discard = all_dst.discard
vert_depths = {}
depth = 0
while stack_old and all_dst:
for ele in stack_old:
for v in verts_from_elem(ele):
len_prev = len(all_dst)
all_dst_discard(v)
if len_prev != len(all_dst):
vert_depths[v] = depth
for ele_other in other_edges_over_cb(ele):
stack_visit_len = len(stack_visit)
stack_visit.add(ele_other)
if stack_visit_len != len(stack_visit):
stack_new.append(ele_other)
stack_new, stack_old = stack_old, stack_new
stack_new[:] = []
depth += 1
if not all_dst:
return [vert_depths[v] for v in ele_dst_verts]
else:
return None
def find_next(ele_dst, ele_src):
depth_src_a = elems_depth_measure(ele_dst, ele_src, other_edges_over_edge)
depth_src_b = elems_depth_measure(ele_dst, ele_src, other_edges_over_face)
# path not found
if depth_src_a is None or depth_src_b is None:
return []
depth_src = tuple(zip(depth_src_a, depth_src_b))
candidates = elems_depth_search(ele_dst, depth_src_a, other_edges_over_edge)
candidates = elems_depth_search(ele_dst, depth_src_b, other_edges_over_face, candidates)
candidates.discard(ele_src)
candidates.discard(ele_dst)
if not candidates:
return []
# Now we have to pick which is the best next-element,
# do this by calculating the element with the largest
# variation in depth from the relationship to the source.
# ... So we have the highest chance of stepping onto the opposite element.
diff_best = 0
ele_best = None
ele_best_ls = []
for ele_test in candidates:
depth_test_a = elems_depth_measure(ele_dst, ele_test, other_edges_over_edge)
depth_test_b = elems_depth_measure(ele_dst, ele_test, other_edges_over_face)
if depth_test_a is None or depth_test_b is None:
continue
depth_test = tuple(zip(depth_test_a, depth_test_b))
# square so a few high values win over many small ones
diff_test = sum((abs(a[0] - b[0]) ** 2) +
(abs(a[1] - b[1]) ** 2) for a, b in zip(depth_src, depth_test))
if diff_test > diff_best:
diff_best = diff_test
ele_best = ele_test
ele_best_ls[:] = [ele_best]
elif diff_test == diff_best:
if ele_best is None:
ele_best = ele_test
ele_best_ls.append(ele_test)
if len(ele_best_ls) > 1:
ele_best_ls_init = ele_best_ls
ele_best_ls = []
depth_accum_max = -1
for ele_test in ele_best_ls_init:
depth_test_a = elems_depth_measure(ele_src, ele_test, other_edges_over_edge)
depth_test_b = elems_depth_measure(ele_src, ele_test, other_edges_over_face)
if depth_test_a is None or depth_test_b is None:
continue
depth_accum_test = (
sum(depth_test_a) + sum(depth_test_b))
if depth_accum_test > depth_accum_max:
depth_accum_max = depth_accum_test
ele_best = ele_test
ele_best_ls[:] = [ele_best]
elif depth_accum_test == depth_accum_max:
# we have multiple bests, don't return any
ele_best_ls.append(ele_test)
return ele_best_ls
# expose for operators
def select_next(bm, report):
import bmesh
ele_pair = [None, None]
for i, ele in enumerate(reversed(bm.select_history)):
ele_pair[i] = ele
if i == 1:
break
if ele_pair[-1] is None:
report({'INFO'}, "Selection pair not found")
return False
ele_pair_next = find_next(*ele_pair)
if len(ele_pair_next) > 1:
# We have multiple options,
# check topology around the element and find the closest match
# (allow for sloppy comparison if exact checks fail).
def ele_uuid(ele):
ele_type = type(ele)
if ele_type is bmesh.types.BMFace:
ret = [len(f.verts) for l in ele.loops for f in l.edge.link_faces if f is not ele]
elif ele_type is bmesh.types.BMEdge:
ret = [len(l.face.verts) for l in ele.link_loops]
elif ele_type is bmesh.types.BMVert:
ret = [len(l.face.verts) for l in ele.link_loops]
else:
raise TypeError("wrong type")
return tuple(sorted(ret))
def ele_uuid_filter():
def pass_fn(seq):
return seq
def sum_set(seq):
return sum(set(seq))
uuid_cmp = ele_uuid(ele_pair[0])
ele_pair_next_uuid = [(ele, ele_uuid(ele)) for ele in ele_pair_next]
# Attempt to find the closest match,
# start specific, use increasingly more approximate comparisons.
for fn in (pass_fn, set, sum_set, len):
uuid_cmp_test = fn(uuid_cmp)
ele_pair_next_uuid_test = [
(ele, uuid) for (ele, uuid) in ele_pair_next_uuid
if uuid_cmp_test == fn(uuid)
]
if len(ele_pair_next_uuid_test) > 1:
ele_pair_next_uuid = ele_pair_next_uuid_test
elif len(ele_pair_next_uuid_test) == 1:
return [ele for (ele, uuid) in ele_pair_next_uuid_test]
return []
ele_pair_next[:] = ele_uuid_filter()
del ele_uuid, ele_uuid_filter
if len(ele_pair_next) != 1:
report({'INFO'}, "No single next item found")
return False
ele = ele_pair_next[0]
if ele.hide:
report({'INFO'}, "Next element is hidden")
return False
ele.select_set(False)
ele.select_set(True)
bm.select_history.discard(ele)
bm.select_history.add(ele)
if type(ele) is bmesh.types.BMFace:
bm.faces.active = ele
return True
def select_prev(bm, report):
import bmesh
for ele in reversed(bm.select_history):
break
else:
report({'INFO'}, "Last selected not found")
return False
ele.select_set(False)
for i, ele in enumerate(reversed(bm.select_history)):
if i == 1:
if type(ele) is bmesh.types.BMFace:
bm.faces.active = ele
break
return True