Files
blender-portable-repo/scripts/addons/RetopoFlow/retopoflow/rftool_strokes/strokes_utils.py
T
2026-03-17 14:30:01 -06:00

197 lines
6.5 KiB
Python

'''
Copyright (C) 2023 CG Cookie
http://cgcookie.com
hello@cgcookie.com
Created by Jonathan Denning, Jonathan Williamson
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import bpy
import math
from mathutils import Vector, Matrix
from mathutils.geometry import intersect_line_line_2d
from ...addon_common.common.debug import dprint
from ...addon_common.common.maths import Point,Point2D,Vec2D,Vec, Normal, clamp
from ...addon_common.common.bezier import CubicBezierSpline, CubicBezier
from ...addon_common.common.utils import iter_pairs
def process_stroke_filter(stroke, min_distance=1.0, max_distance=2.0):
''' filter stroke to pts that are at least min_distance apart '''
nstroke = stroke[:1]
for p in stroke[1:]:
v = p - nstroke[-1]
l = v.length
if l < min_distance: continue
d = v / l
while l > 0:
q = nstroke[-1] + d * min(l, max_distance)
nstroke.append(q)
l -= max_distance
return nstroke
def process_stroke_source(stroke, raycast, Point_to_Point2D=None, is_point_on_mirrored_side=None, mirror_point=None, clamp_point_to_symmetry=None):
''' filter out pts that don't hit source on non-mirrored side '''
pts = [(pt, raycast(pt)[0]) for pt in stroke]
pts = [(pt, p3d) for (pt, p3d) in pts if p3d]
if Point_to_Point2D and mirror_point:
pts_ = [Point_to_Point2D(mirror_point(p3d)) for (_, p3d) in pts]
pts = [(pt, raycast(pt)[0]) for pt in pts_]
pts = [(pt, p3d) for (pt, p3d) in pts if p3d]
if Point_to_Point2D and clamp_point_to_symmetry:
pts_ = [Point_to_Point2D(clamp_point_to_symmetry(p3d)) for (_, p3d) in pts]
pts = [(pt, raycast(pt)[0]) for pt in pts_]
pts = [(pt, p3d) for (pt, p3d) in pts if p3d]
if is_point_on_mirrored_side:
pts = [(pt, p3d) for (pt, p3d) in pts if not is_point_on_mirrored_side(p3d)]
return [pt for (pt, _) in pts]
def find_edge_cycles(edges):
edges = set(edges)
verts = {v: set() for e in edges for v in e.verts}
for e in edges:
for v in e.verts:
verts[v].add(e)
in_cycle = set()
for vstart in verts:
if vstart in in_cycle: continue
for estart in vstart.link_edges:
if estart not in edges: continue
if estart in in_cycle: continue
q = [(estart, vstart, None)]
found = None
trace = {}
while q:
ec, vc, ep = q.pop(0)
if ec in trace: continue
trace[ec] = (vc, ep)
vn = ec.other_vert(vc)
if vn == vstart:
found = ec
break
q += [(en, vn, ec) for en in vn.link_edges if en in edges]
if not found: continue
l = [found]
in_cycle.add(found)
while True:
vn, ep = trace[l[-1]]
in_cycle.add(vn)
in_cycle.add(ep)
if vn == vstart: break
l.append(ep)
yield l
def find_edge_strips(edges):
''' find edge strips '''
edges = set(edges)
verts = {v: set() for e in edges for v in e.verts}
for e in edges:
for v in e.verts:
verts[v].add(e)
ends = [v for v in verts if len(verts[v]) == 1]
def get_edge_sequence(v0, v1):
trace = {}
q = [(None, v0)]
while q:
vf,vt = q.pop(0)
if vt in trace: continue
trace[vt] = vf
if vt == v1: break
for e in verts[vt]:
q.append((vt, e.other_vert(vt)))
if v1 not in trace: return []
l = []
while v1 is not None:
l.append(v1)
v1 = trace[v1]
l.reverse()
return [v0.shared_edge(v1) for (v0, v1) in iter_pairs(l, wrap=False)]
for i0 in range(len(ends)):
for i1 in range(i0+1,len(ends)):
l = get_edge_sequence(ends[i0], ends[i1])
if l: yield l
def get_strip_verts(edge_strip):
l = len(edge_strip)
if l == 0: return []
if l == 1:
e = edge_strip[0]
return list(e.verts) if e.is_valid else []
vs = []
for e0, e1 in iter_pairs(edge_strip, wrap=False):
vs.append(e0.shared_vert(e1))
vs = [edge_strip[0].other_vert(vs[0])] + vs + [edge_strip[-1].other_vert(vs[-1])]
return vs
def restroke(stroke, percentages):
lens = [(s0 - s1).length for (s0, s1) in iter_pairs(stroke, wrap=False)]
total_len = sum(lens)
stops = [max(0, min(1, p)) * total_len for p in percentages]
dist = 0
istroke = 0
istop = 0
nstroke = []
while istroke + 1 < len(stroke) and istop < len(stops):
if lens[istroke] <= 0:
istroke += 1
continue
t = (stops[istop] - dist) / lens[istroke]
if t < 0:
istop += 1
elif t > 1.000001:
dist += lens[istroke]
istroke += 1
else:
s0, s1 = stroke[istroke], stroke[istroke + 1]
nstroke.append(s0 + (s1 - s0) * t)
istop += 1
return nstroke
def walk_to_corner(from_vert, to_edges):
to_verts = {v for e in to_edges for v in e.verts}
edges = [
(e, from_vert, None)
for e in from_vert.link_edges
if not e.is_manifold and e.is_valid
]
touched = {}
found = None
while edges:
ec, v0, ep = edges.pop(0)
if ec in touched: continue
touched[ec] = (v0, ep)
v1 = ec.other_vert(v0)
if v1 in to_verts:
found = ec
break
nedges = [
(en, v1, ec)
for en in v1.link_edges
if en != ec and not en.is_manifold and en.is_valid
]
edges += nedges
if not found: return None
# walk back
walk = [found]
while True:
ec = walk[-1]
v0, ep = touched[ec]
if v0 == from_vert:
break
walk.append(ep)
return walk