[#20046] 2.5 "bridge faces" tool doesn't delete original selection
remove this script, its not good enough, remove grease pencil retopo also.
This commit is contained in:
@@ -1,503 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
EPS_SPLINE_DIV = 15.0 # remove doubles is ~15th the length of the spline
|
||||
ANGLE_JOIN_LIMIT = 25.0 # limit for joining splines into 1.
|
||||
|
||||
def get_hub(co, _hubs, EPS_SPLINE):
|
||||
|
||||
if 1:
|
||||
for hub in _hubs.values():
|
||||
if (hub.co - co).length < EPS_SPLINE:
|
||||
return hub
|
||||
|
||||
key = co.to_tuple(3)
|
||||
hub = _hubs[key] = Hub(co, key, len(_hubs))
|
||||
return hub
|
||||
else:
|
||||
pass
|
||||
|
||||
'''
|
||||
key = co.to_tuple(3)
|
||||
try:
|
||||
return _hubs[key]
|
||||
except:
|
||||
hub = _hubs[key] = Hub(co, key, len(_hubs))
|
||||
return hub
|
||||
'''
|
||||
|
||||
|
||||
class Hub(object):
|
||||
__slots__ = "co", "key", "index", "links"
|
||||
|
||||
def __init__(self, co, key, index):
|
||||
self.co = co.copy()
|
||||
self.key = key
|
||||
self.index = index
|
||||
self.links = []
|
||||
|
||||
def get_weight(self):
|
||||
f = 0.0
|
||||
|
||||
for hub_other in self.links:
|
||||
f += (self.co - hub_other.co).length
|
||||
|
||||
def replace(self, other):
|
||||
for hub in self.links:
|
||||
try:
|
||||
hub.links.remove(self)
|
||||
except:
|
||||
pass
|
||||
if other not in hub.links:
|
||||
hub.links.append(other)
|
||||
|
||||
def dist(self, other):
|
||||
return (self.co - other.co).length
|
||||
|
||||
def calc_faces(self, hub_ls):
|
||||
faces = []
|
||||
# first tris
|
||||
for l_a in self.links:
|
||||
for l_b in l_a.links:
|
||||
if l_b is not self and l_b in self.links:
|
||||
# will give duplicates
|
||||
faces.append((self.index, l_a.index, l_b.index))
|
||||
|
||||
# now quads, check which links share 2 different verts directly
|
||||
def validate_quad(face):
|
||||
if len(set(face)) != len(face):
|
||||
return False
|
||||
if hub_ls[face[0]] in hub_ls[face[2]].links:
|
||||
return False
|
||||
if hub_ls[face[2]] in hub_ls[face[0]].links:
|
||||
return False
|
||||
|
||||
if hub_ls[face[1]] in hub_ls[face[3]].links:
|
||||
return False
|
||||
if hub_ls[face[3]] in hub_ls[face[1]].links:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
for i, l_a in enumerate(self.links):
|
||||
links_a = {l.index for l in l_a.links}
|
||||
for j in range(i):
|
||||
l_b = self.links[j]
|
||||
|
||||
links_b = {l.index for l in l_b.links}
|
||||
|
||||
isect = links_a.intersection(links_b)
|
||||
if len(isect) == 2:
|
||||
isect = list(isect)
|
||||
|
||||
# check there are no diagonal lines
|
||||
face = (isect[0], l_a.index, isect[1], l_b.index)
|
||||
if validate_quad(face):
|
||||
|
||||
faces.append(face)
|
||||
|
||||
return faces
|
||||
|
||||
|
||||
class BBox(object):
|
||||
__slots__ = "xmin", "ymin", "zmin", "xmax", "ymax", "zmax"
|
||||
|
||||
def __init__(self):
|
||||
self.xmin = self.ymin = self.zmin = 100000000.0
|
||||
self.xmax = self.ymax = self.zmax = -100000000.0
|
||||
|
||||
@property
|
||||
def xdim(self):
|
||||
return self.xmax - self.xmin
|
||||
|
||||
@property
|
||||
def ydim(self):
|
||||
return self.ymax - self.ymin
|
||||
|
||||
@property
|
||||
def zdim(self):
|
||||
return self.zmax - self.zmin
|
||||
|
||||
def calc(self, points):
|
||||
xmin = ymin = zmin = 100000000.0
|
||||
xmax = ymax = zmax = -100000000.0
|
||||
|
||||
for pt in points:
|
||||
x, y, z = pt
|
||||
if x < xmin:
|
||||
xmin = x
|
||||
if y < ymin:
|
||||
ymin = y
|
||||
if z < zmin:
|
||||
zmin = z
|
||||
|
||||
if x > xmax:
|
||||
xmax = x
|
||||
if y > ymax:
|
||||
ymax = y
|
||||
if z > zmax:
|
||||
zmax = z
|
||||
|
||||
self.xmin, self.ymin, self.zmin = xmin, ymin, zmin
|
||||
self.xmax, self.ymax, self.zmax = xmax, ymax, zmax
|
||||
|
||||
def xsect(self, other, margin=0.0):
|
||||
if margin == 0.0:
|
||||
if self.xmax < other.xmin:
|
||||
return False
|
||||
if self.ymax < other.ymin:
|
||||
return False
|
||||
if self.zmax < other.zmin:
|
||||
return False
|
||||
|
||||
if self.xmin > other.xmax:
|
||||
return False
|
||||
if self.ymin > other.ymax:
|
||||
return False
|
||||
if self.zmin > other.zmax:
|
||||
return False
|
||||
|
||||
else:
|
||||
xmargin = ((self.xdim + other.xdim) / 2.0) * margin
|
||||
ymargin = ((self.ydim + other.ydim) / 2.0) * margin
|
||||
zmargin = ((self.zdim + other.zdim) / 2.0) * margin
|
||||
|
||||
if self.xmax < other.xmin - xmargin:
|
||||
return False
|
||||
if self.ymax < other.ymin - ymargin:
|
||||
return False
|
||||
if self.zmax < other.zmin - zmargin:
|
||||
return False
|
||||
|
||||
if self.xmin > other.xmax + xmargin:
|
||||
return False
|
||||
if self.ymin > other.ymax + ymargin:
|
||||
return False
|
||||
if self.zmin > other.zmax + zmargin:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.xmin = min(self.xmin, other.xmin)
|
||||
self.ymin = min(self.ymin, other.ymin)
|
||||
self.zmin = min(self.zmin, other.zmin)
|
||||
|
||||
self.xmax = max(self.xmax, other.xmax)
|
||||
self.ymax = max(self.ymax, other.ymax)
|
||||
self.zmax = max(self.zmax, other.zmax)
|
||||
return self
|
||||
|
||||
class Spline(object):
|
||||
__slots__ = "points", "hubs", "length", "bb"
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.hubs = []
|
||||
self.calc_length()
|
||||
self.bb = BBox()
|
||||
self.bb.calc(points)
|
||||
|
||||
def calc_length(self):
|
||||
# calc length
|
||||
f = 0.0
|
||||
co_prev = self.points[0]
|
||||
for co in self.points[1:]:
|
||||
f += (co - co_prev).length
|
||||
co_prev = co
|
||||
self.length = f
|
||||
|
||||
def link(self):
|
||||
if len(self.hubs) < 2:
|
||||
return
|
||||
|
||||
edges = list(set([i for i, hub in self.hubs]))
|
||||
edges.sort()
|
||||
|
||||
edges_order = {}
|
||||
for i in edges:
|
||||
edges_order[i] = []
|
||||
|
||||
|
||||
# self.hubs.sort()
|
||||
for i, hub in self.hubs:
|
||||
edges_order[i].append(hub)
|
||||
|
||||
hubs_order = []
|
||||
for i in edges:
|
||||
ls = edges_order[i]
|
||||
edge_start = self.points[i]
|
||||
ls.sort(key=lambda hub: (hub.co - edge_start).length)
|
||||
hubs_order.extend(ls)
|
||||
|
||||
# Now we have the order, connect the hubs
|
||||
hub_prev = hubs_order[0]
|
||||
|
||||
for hub in hubs_order[1:]:
|
||||
hub.links.append(hub_prev)
|
||||
hub_prev.links.append(hub)
|
||||
hub_prev = hub
|
||||
|
||||
|
||||
def get_points(stroke):
|
||||
return [point.co.copy() for point in stroke.points]
|
||||
|
||||
|
||||
def get_splines(gp):
|
||||
l = None
|
||||
for l in gp.layers:
|
||||
if l.active: # XXX - should be layers.active
|
||||
break
|
||||
if l:
|
||||
frame = l.active_frame
|
||||
return [Spline(get_points(stroke)) for stroke in frame.strokes]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def xsect_spline(sp_a, sp_b, _hubs):
|
||||
from Geometry import ClosestPointOnLine, LineIntersect
|
||||
pt_a_prev = pt_b_prev = None
|
||||
EPS_SPLINE = (sp_a.length + sp_b.length) / (EPS_SPLINE_DIV * 2)
|
||||
pt_a_prev = sp_a.points[0]
|
||||
for a, pt_a in enumerate(sp_a.points[1:]):
|
||||
pt_b_prev = sp_b.points[0]
|
||||
for b, pt_b in enumerate(sp_b.points[1:]):
|
||||
|
||||
# Now we have 2 edges
|
||||
# print(pt_a, pt_a_prev, pt_b, pt_b_prev)
|
||||
xsect = LineIntersect(pt_a, pt_a_prev, pt_b, pt_b_prev)
|
||||
if xsect is not None:
|
||||
if (xsect[0] - xsect[1]).length <= EPS_SPLINE:
|
||||
f = ClosestPointOnLine(xsect[1], pt_a, pt_a_prev)[1]
|
||||
# if f >= 0.0-EPS_SPLINE and f <= 1.0+EPS_SPLINE: # for some reason doesnt work so well, same below
|
||||
if f >= 0.0 and f <= 1.0:
|
||||
f = ClosestPointOnLine(xsect[0], pt_b, pt_b_prev)[1]
|
||||
# if f >= 0.0-EPS_SPLINE and f <= 1.0+EPS_SPLINE:
|
||||
if f >= 0.0 and f <= 1.0:
|
||||
# This wont happen often
|
||||
co = xsect[0].lerp(xsect[1], 0.5)
|
||||
hub = get_hub(co, _hubs, EPS_SPLINE)
|
||||
|
||||
sp_a.hubs.append((a, hub))
|
||||
sp_b.hubs.append((b, hub))
|
||||
|
||||
pt_b_prev = pt_b
|
||||
|
||||
pt_a_prev = pt_a
|
||||
|
||||
|
||||
def connect_splines(splines):
|
||||
HASH_PREC = 8
|
||||
from math import radians
|
||||
ANG_LIMIT = radians(ANGLE_JOIN_LIMIT)
|
||||
def sort_pair(a, b):
|
||||
if a < b:
|
||||
return a, b
|
||||
else:
|
||||
return b, a
|
||||
|
||||
#def test_join(p1a, p1b, p2a, p2b, length_average):
|
||||
def test_join(s1, s2, dir1, dir2, length_average):
|
||||
if dir1 is False:
|
||||
p1a = s1.points[0]
|
||||
p1b = s1.points[1]
|
||||
else:
|
||||
p1a = s1.points[-1]
|
||||
p1b = s1.points[-2]
|
||||
|
||||
if dir2 is False:
|
||||
p2a = s2.points[0]
|
||||
p2b = s2.points[1]
|
||||
else:
|
||||
p2a = s2.points[-1]
|
||||
p2b = s2.points[-2]
|
||||
|
||||
# compare length between tips
|
||||
if (p1a - p2a).length > (length_average / EPS_SPLINE_DIV):
|
||||
return False
|
||||
|
||||
v1 = p1a - p1b
|
||||
v2 = p2b - p2a
|
||||
|
||||
if v1.angle(v2) > ANG_LIMIT:
|
||||
return False
|
||||
|
||||
# print("joining!")
|
||||
return True
|
||||
|
||||
# lazy, hash the points that have been compared.
|
||||
comparisons = set()
|
||||
|
||||
do_join = True
|
||||
while do_join:
|
||||
do_join = False
|
||||
for i, s1 in enumerate(splines):
|
||||
key1a = s1.points[0].to_tuple(HASH_PREC)
|
||||
key1b = s1.points[-1].to_tuple(HASH_PREC)
|
||||
|
||||
for j, s2 in enumerate(splines):
|
||||
if s1 is s2:
|
||||
continue
|
||||
|
||||
length_average = min(s1.length, s2.length)
|
||||
|
||||
key2a = s2.points[0].to_tuple(HASH_PREC)
|
||||
key2b = s2.points[-1].to_tuple(HASH_PREC)
|
||||
|
||||
# there are 4 ways this may be joined
|
||||
key_pair = sort_pair(key1a, key2a)
|
||||
if key_pair not in comparisons:
|
||||
comparisons.add(key_pair)
|
||||
if test_join(s1, s2, False, False, length_average):
|
||||
s1.points[:0] = reversed(s2.points)
|
||||
s1.bb += s2.bb
|
||||
s1.calc_length()
|
||||
del splines[j]
|
||||
do_join = True
|
||||
break
|
||||
|
||||
key_pair = sort_pair(key1a, key2b)
|
||||
if key_pair not in comparisons:
|
||||
comparisons.add(key_pair)
|
||||
if test_join(s1, s2, False, True, length_average):
|
||||
s1.points[:0] = s2.points
|
||||
s1.bb += s2.bb
|
||||
s1.calc_length()
|
||||
del splines[j]
|
||||
do_join = True
|
||||
break
|
||||
|
||||
key_pair = sort_pair(key1b, key2b)
|
||||
if key_pair not in comparisons:
|
||||
comparisons.add(key_pair)
|
||||
if test_join(s1, s2, True, True, length_average):
|
||||
s1.points += list(reversed(s2.points))
|
||||
s1.bb += s2.bb
|
||||
s1.calc_length()
|
||||
del splines[j]
|
||||
do_join = True
|
||||
break
|
||||
|
||||
key_pair = sort_pair(key1b, key2a)
|
||||
if key_pair not in comparisons:
|
||||
comparisons.add(key_pair)
|
||||
if test_join(s1, s2, True, False, length_average):
|
||||
s1.points += s2.points
|
||||
s1.bb += s2.bb
|
||||
s1.calc_length()
|
||||
del splines[j]
|
||||
do_join = True
|
||||
break
|
||||
|
||||
if do_join:
|
||||
break
|
||||
|
||||
|
||||
def calculate(gp):
|
||||
splines = get_splines(gp)
|
||||
|
||||
# spline endpoints may be co-linear, join these into single splines
|
||||
connect_splines(splines)
|
||||
|
||||
_hubs = {}
|
||||
|
||||
for i, sp in enumerate(splines):
|
||||
for j, sp_other in enumerate(splines):
|
||||
if j <= i:
|
||||
continue
|
||||
|
||||
if sp.bb.xsect(sp_other.bb, margin=0.1):
|
||||
xsect_spline(sp, sp_other, _hubs)
|
||||
|
||||
for sp in splines:
|
||||
sp.link()
|
||||
|
||||
# remove these
|
||||
hubs_ls = [hub for hub in _hubs.values() if hub.index != -1]
|
||||
|
||||
_hubs.clear()
|
||||
_hubs = None
|
||||
|
||||
for i, hub in enumerate(hubs_ls):
|
||||
hub.index = i
|
||||
|
||||
# Now we have connected hubs, write all edges!
|
||||
def order(i1, i2):
|
||||
if i1 > i2:
|
||||
return i2, i1
|
||||
return i1, i2
|
||||
|
||||
edges = {}
|
||||
|
||||
for hub in hubs_ls:
|
||||
i1 = hub.index
|
||||
for hub_other in hub.links:
|
||||
i2 = hub_other.index
|
||||
edges[order(i1, i2)] = None
|
||||
|
||||
verts = []
|
||||
edges = edges.keys()
|
||||
faces = []
|
||||
|
||||
for hub in hubs_ls:
|
||||
verts.append(hub.co)
|
||||
faces.extend(hub.calc_faces(hubs_ls))
|
||||
|
||||
# remove double faces
|
||||
faces = dict([(tuple(sorted(f)), f) for f in faces]).values()
|
||||
|
||||
mesh = bpy.data.meshes.new("Retopo")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
|
||||
scene = bpy.context.scene
|
||||
mesh.update()
|
||||
obj_new = bpy.data.objects.new("Torus", 'MESH')
|
||||
obj_new.data = mesh
|
||||
scene.objects.link(obj_new)
|
||||
|
||||
return obj_new
|
||||
|
||||
|
||||
def main():
|
||||
scene = bpy.context.scene
|
||||
obj = bpy.context.object
|
||||
|
||||
gp = None
|
||||
|
||||
if obj:
|
||||
gp = obj.grease_pencil
|
||||
|
||||
if not gp:
|
||||
gp = scene.grease_pencil
|
||||
|
||||
if not gp:
|
||||
raise Exception("no active grease pencil")
|
||||
|
||||
obj_new = calculate(gp)
|
||||
|
||||
scene.objects.active = obj_new
|
||||
obj_new.selected = True
|
||||
|
||||
# nasty, recalc normals
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
bpy.ops.mesh.normals_make_consistent(inside=False)
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
@@ -1,655 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# import Blender
|
||||
import time, functools
|
||||
import bpy
|
||||
# from Blender import Window
|
||||
from Mathutils import Vector
|
||||
# import BPyMessages
|
||||
|
||||
# from Blender.Draw import PupMenu
|
||||
|
||||
BIG_NUM = 1<<30
|
||||
|
||||
global CULL_METHOD
|
||||
CULL_METHOD = 0
|
||||
|
||||
def AngleBetweenVecs(a1,a2):
|
||||
import math
|
||||
try:
|
||||
return math.degrees(a1.angle(a2))
|
||||
except:
|
||||
return 180.0
|
||||
|
||||
class edge(object):
|
||||
__slots__ = 'v1', 'v2', 'co1', 'co2', 'length', 'removed', 'match', 'cent', 'angle', 'next', 'prev', 'normal', 'fake'
|
||||
def __init__(self, v1,v2):
|
||||
self.v1 = v1
|
||||
self.v2 = v2
|
||||
co1, co2= v1.co, v2.co
|
||||
self.co1= co1
|
||||
self.co2= co2
|
||||
|
||||
# uv1 uv2 vcol1 vcol2 # Add later
|
||||
self.length = (co1 - co2).length
|
||||
self.removed = 0 # Have we been culled from the eloop
|
||||
self.match = None # The other edge were making a face with
|
||||
|
||||
self.cent= co1.lerp(co2, 0.5)
|
||||
self.angle= 0.0
|
||||
self.fake= False
|
||||
|
||||
class edgeLoop(object):
|
||||
__slots__ = 'centre', 'edges', 'normal', 'closed', 'backup_edges'
|
||||
def __init__(self, loop, me, closed): # Vert loop
|
||||
# Use next and prev, nextDist, prevDist
|
||||
|
||||
# Get Loops centre.
|
||||
fac= len(loop)
|
||||
verts = me.verts
|
||||
self.centre= functools.reduce(lambda a,b: a+verts[b].co/fac, loop, Vector())
|
||||
|
||||
# Convert Vert loop to Edges.
|
||||
self.edges = [edge(verts[loop[vIdx-1]], verts[loop[vIdx]]) for vIdx in range(len(loop))]
|
||||
|
||||
if not closed:
|
||||
self.edges[0].fake = True # fake edge option
|
||||
|
||||
self.closed = closed
|
||||
|
||||
|
||||
# Assign linked list
|
||||
for eIdx in range(len(self.edges)-1):
|
||||
self.edges[eIdx].next = self.edges[eIdx+1]
|
||||
self.edges[eIdx].prev = self.edges[eIdx-1]
|
||||
# Now last
|
||||
self.edges[-1].next = self.edges[0]
|
||||
self.edges[-1].prev = self.edges[-2]
|
||||
|
||||
|
||||
|
||||
# GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP.
|
||||
self.normal = Vector()
|
||||
for e in self.edges:
|
||||
n = (self.centre-e.co1).cross(self.centre-e.co2)
|
||||
# Do we realy need tot normalize?
|
||||
n.normalize()
|
||||
self.normal += n
|
||||
|
||||
# Generate the angle
|
||||
va= e.cent - e.prev.cent
|
||||
vb= e.next.cent - e.cent
|
||||
|
||||
e.angle= AngleBetweenVecs(va, vb)
|
||||
|
||||
# Blur the angles
|
||||
#for e in self.edges:
|
||||
# e.angle= (e.angle+e.next.angle)/2
|
||||
|
||||
# Blur the angles
|
||||
#for e in self.edges:
|
||||
# e.angle= (e.angle+e.prev.angle)/2
|
||||
|
||||
self.normal.normalize()
|
||||
|
||||
# Generate a normal for each edge.
|
||||
for e in self.edges:
|
||||
|
||||
n1 = e.co1
|
||||
n2 = e.co2
|
||||
n3 = e.prev.co1
|
||||
|
||||
a = n1-n2
|
||||
b = n1-n3
|
||||
normal1 = a.cross(b)
|
||||
normal1.normalize()
|
||||
|
||||
n1 = e.co2
|
||||
n3 = e.next.co2
|
||||
n2 = e.co1
|
||||
|
||||
a = n1-n2
|
||||
b = n1-n3
|
||||
|
||||
normal2 = a.cross(b)
|
||||
normal2.normalize()
|
||||
|
||||
# Reuse normal1 var
|
||||
normal1 += normal1 + normal2
|
||||
normal1.normalize()
|
||||
|
||||
e.normal = normal1
|
||||
#print e.normal
|
||||
|
||||
|
||||
|
||||
def backup(self):
|
||||
# Keep a backup of the edges
|
||||
self.backup_edges = self.edges[:]
|
||||
|
||||
def restore(self):
|
||||
self.edges = self.backup_edges[:]
|
||||
for e in self.edges:
|
||||
e.removed = 0
|
||||
|
||||
def reverse(self):
|
||||
self.edges.reverse()
|
||||
self.normal.negate()
|
||||
|
||||
for e in self.edges:
|
||||
e.normal.negate()
|
||||
e.v1, e.v2 = e.v2, e.v1
|
||||
e.co1, e.co2 = e.co2, e.co1
|
||||
e.next, e.prev = e.prev, e.next
|
||||
|
||||
|
||||
def removeSmallest(self, cullNum, otherLoopLen):
|
||||
'''
|
||||
Removes N Smallest edges and backs up the loop,
|
||||
this is so we can loop between 2 loops as if they are the same length,
|
||||
backing up and restoring incase the loop needs to be skinned with another loop of a different length.
|
||||
'''
|
||||
global CULL_METHOD
|
||||
if CULL_METHOD == 1: # Shortest edge
|
||||
eloopCopy = self.edges[:]
|
||||
|
||||
# Length sort, smallest first
|
||||
try: eloopCopy.sort(key = lambda e1: e1.length)
|
||||
except: eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length ))
|
||||
|
||||
# Dont use atm
|
||||
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle*e1.length, e2.angle*e2.length)) # Length sort, smallest first
|
||||
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle, e2.angle)) # Length sort, smallest first
|
||||
|
||||
remNum = 0
|
||||
for i, e in enumerate(eloopCopy):
|
||||
if not e.fake:
|
||||
e.removed = 1
|
||||
self.edges.remove( e ) # Remove from own list, still in linked list.
|
||||
remNum += 1
|
||||
|
||||
if not remNum < cullNum:
|
||||
break
|
||||
|
||||
else: # CULL METHOD is even
|
||||
|
||||
culled = 0
|
||||
|
||||
step = int(otherLoopLen / float(cullNum)) * 2
|
||||
|
||||
currentEdge = self.edges[0]
|
||||
while culled < cullNum:
|
||||
|
||||
# Get the shortest face in the next STEP
|
||||
step_count= 0
|
||||
bestAng= 360.0
|
||||
smallestEdge= None
|
||||
while step_count<=step or smallestEdge==None:
|
||||
step_count+=1
|
||||
if not currentEdge.removed: # 0 or -1 will not be accepted
|
||||
if currentEdge.angle<bestAng and not currentEdge.fake:
|
||||
smallestEdge= currentEdge
|
||||
bestAng= currentEdge.angle
|
||||
|
||||
currentEdge = currentEdge.next
|
||||
|
||||
# In that stepping length we have the smallest edge.remove it
|
||||
smallestEdge.removed = 1
|
||||
self.edges.remove(smallestEdge)
|
||||
|
||||
# Start scanning from the edge we found? - result is over fanning- no good.
|
||||
#currentEdge= smallestEdge.next
|
||||
|
||||
culled+=1
|
||||
|
||||
|
||||
# Returns face edges.
|
||||
# face must have edge data.
|
||||
|
||||
def mesh_faces_extend(me, faces, mat_idx = 0):
|
||||
orig_facetot = len(me.faces)
|
||||
new_facetot = len(faces)
|
||||
me.add_geometry(0, 0, new_facetot)
|
||||
tot = orig_facetot+new_facetot
|
||||
me_faces = me.faces
|
||||
i= 0
|
||||
while i < new_facetot:
|
||||
|
||||
f = [v.index for v in faces[i]]
|
||||
if len(f)==4:
|
||||
if f[3]==0:
|
||||
f = f[1], f[2], f[3], f[0]
|
||||
else:
|
||||
f = f[0], f[1], f[2], 0
|
||||
|
||||
mf = me_faces[orig_facetot+i]
|
||||
mf.verts_raw = f
|
||||
mf.material_index = mat_idx
|
||||
i+=1
|
||||
# end utils
|
||||
|
||||
|
||||
def getSelectedEdges(context, me, ob):
|
||||
MESH_MODE = tuple(context.tool_settings.mesh_selection_mode)
|
||||
context.tool_settings.mesh_selection_mode = False, True, False
|
||||
|
||||
if not MESH_MODE[2]:
|
||||
edges= [ ed for ed in me.edges if ed.selected ]
|
||||
# print len(edges), len(me.edges)
|
||||
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
|
||||
return edges
|
||||
|
||||
else:
|
||||
# value is [edge, face_sel_user_in]
|
||||
edge_dict= dict((ed.key, [ed, 0]) for ed in me.edges)
|
||||
|
||||
for f in me.faces:
|
||||
if f.selected:
|
||||
for edkey in f.edge_keys:
|
||||
edge_dict[edkey][1] += 1
|
||||
|
||||
context.tool_settings.mesh_selection_mode = MESH_MODE
|
||||
return [ ed_data[0] for ed_data in edge_dict.values() if ed_data[1] == 1 ]
|
||||
|
||||
|
||||
|
||||
def getVertLoops(selEdges, me):
|
||||
'''
|
||||
return a list of vert loops, closed and open [(loop, closed)...]
|
||||
'''
|
||||
|
||||
mainVertLoops = []
|
||||
# second method
|
||||
tot = len(me.verts)
|
||||
vert_siblings = [[] for i in range(tot)]
|
||||
vert_used = [False] * tot
|
||||
|
||||
for ed in selEdges:
|
||||
i1, i2 = ed.key
|
||||
vert_siblings[i1].append(i2)
|
||||
vert_siblings[i2].append(i1)
|
||||
|
||||
# find the first used vert and keep looping.
|
||||
for i in range(tot):
|
||||
if vert_siblings[i] and not vert_used[i]:
|
||||
sbl = vert_siblings[i] # siblings
|
||||
|
||||
if len(sbl) > 2:
|
||||
return None
|
||||
|
||||
vert_used[i] = True
|
||||
|
||||
# do an edgeloop seek
|
||||
if len(sbl) == 2:
|
||||
contextVertLoop= [sbl[0], i, sbl[1]] # start the vert loop
|
||||
vert_used[contextVertLoop[ 0]] = True
|
||||
vert_used[contextVertLoop[-1]] = True
|
||||
else:
|
||||
contextVertLoop= [i, sbl[0]]
|
||||
vert_used[contextVertLoop[ 1]] = True
|
||||
|
||||
# Always seek up
|
||||
ok = True
|
||||
while ok:
|
||||
ok = False
|
||||
closed = False
|
||||
sbl = vert_siblings[contextVertLoop[-1]]
|
||||
if len(sbl) == 2:
|
||||
next = sbl[not sbl.index( contextVertLoop[-2] )]
|
||||
if vert_used[next]:
|
||||
closed = True
|
||||
# break
|
||||
else:
|
||||
contextVertLoop.append( next ) # get the vert that isnt the second last
|
||||
vert_used[next] = True
|
||||
ok = True
|
||||
|
||||
# Seek down as long as the starting vert was not at the edge.
|
||||
if not closed and len(vert_siblings[i]) == 2:
|
||||
|
||||
ok = True
|
||||
while ok:
|
||||
ok = False
|
||||
sbl = vert_siblings[contextVertLoop[0]]
|
||||
if len(sbl) == 2:
|
||||
next = sbl[not sbl.index( contextVertLoop[1] )]
|
||||
if vert_used[next]:
|
||||
closed = True
|
||||
else:
|
||||
contextVertLoop.insert(0, next) # get the vert that isnt the second last
|
||||
vert_used[next] = True
|
||||
ok = True
|
||||
|
||||
mainVertLoops.append((contextVertLoop, closed))
|
||||
|
||||
|
||||
verts = me.verts
|
||||
# convert from indicies to verts
|
||||
# mainVertLoops = [([verts[i] for i in contextVertLoop], closed) for contextVertLoop, closed in mainVertLoops]
|
||||
# print len(mainVertLoops)
|
||||
return mainVertLoops
|
||||
|
||||
|
||||
|
||||
def skin2EdgeLoops(eloop1, eloop2, me, ob, MODE):
|
||||
|
||||
new_faces= [] #
|
||||
|
||||
# Make sure e1 loops is bigger then e2
|
||||
if len(eloop1.edges) != len(eloop2.edges):
|
||||
if len(eloop1.edges) < len(eloop2.edges):
|
||||
eloop1, eloop2 = eloop2, eloop1
|
||||
|
||||
eloop1.backup() # were about to cull faces
|
||||
CULL_FACES = len(eloop1.edges) - len(eloop2.edges)
|
||||
eloop1.removeSmallest(CULL_FACES, len(eloop1.edges))
|
||||
else:
|
||||
CULL_FACES = 0
|
||||
# First make sure poly vert loops are in sync with eachother.
|
||||
|
||||
# The vector allong which we are skinning.
|
||||
skinVector = eloop1.centre - eloop2.centre
|
||||
|
||||
loopDist = skinVector.length
|
||||
|
||||
# IS THE LOOP FLIPPED, IF SO FLIP BACK. we keep it flipped, its ok,
|
||||
if eloop1.closed or eloop2.closed:
|
||||
angleBetweenLoopNormals = AngleBetweenVecs(eloop1.normal, eloop2.normal)
|
||||
if angleBetweenLoopNormals > 90:
|
||||
eloop2.reverse()
|
||||
|
||||
|
||||
DIR= eloop1.centre - eloop2.centre
|
||||
|
||||
# if eloop2.closed:
|
||||
bestEloopDist = BIG_NUM
|
||||
bestOffset = 0
|
||||
# Loop rotation offset to test.1
|
||||
eLoopIdxs = list(range(len(eloop1.edges)))
|
||||
for offset in range(len(eloop1.edges)):
|
||||
totEloopDist = 0 # Measure this total distance for thsi loop.
|
||||
|
||||
offsetIndexLs = eLoopIdxs[offset:] + eLoopIdxs[:offset] # Make offset index list
|
||||
|
||||
|
||||
# e1Idx is always from 0uu to N, e2Idx is offset.
|
||||
for e1Idx, e2Idx in enumerate(offsetIndexLs):
|
||||
e1= eloop1.edges[e1Idx]
|
||||
e2= eloop2.edges[e2Idx]
|
||||
|
||||
|
||||
# Include fan connections in the measurement.
|
||||
OK= True
|
||||
while OK or e1.removed:
|
||||
OK= False
|
||||
|
||||
# Measure the vloop distance ===============
|
||||
diff= ((e1.cent - e2.cent).length) #/ nangle1
|
||||
|
||||
ed_dir= e1.cent-e2.cent
|
||||
a_diff= AngleBetweenVecs(DIR, ed_dir)/18 # 0 t0 18
|
||||
|
||||
totEloopDist += (diff * (1+a_diff)) / (1+loopDist)
|
||||
|
||||
# Premeture break if where no better off
|
||||
if totEloopDist > bestEloopDist:
|
||||
break
|
||||
|
||||
e1=e1.next
|
||||
|
||||
if totEloopDist < bestEloopDist:
|
||||
bestOffset = offset
|
||||
bestEloopDist = totEloopDist
|
||||
|
||||
# Modify V2 LS for Best offset
|
||||
eloop2.edges = eloop2.edges[bestOffset:] + eloop2.edges[:bestOffset]
|
||||
|
||||
else:
|
||||
# Both are open loops, easier to calculate.
|
||||
|
||||
|
||||
# Make sure the fake edges are at the start.
|
||||
for i, edloop in enumerate((eloop1, eloop2)):
|
||||
# print "LOOPO"
|
||||
if edloop.edges[0].fake:
|
||||
# alredy at the start
|
||||
#print "A"
|
||||
pass
|
||||
elif edloop.edges[-1].fake:
|
||||
# put the end at the start
|
||||
edloop.edges.insert(0, edloop.edges.pop())
|
||||
#print "B"
|
||||
|
||||
else:
|
||||
for j, ed in enumerate(edloop.edges):
|
||||
if ed.fake:
|
||||
#print "C"
|
||||
edloop.edges = edloop.edges = edloop.edges[j:] + edloop.edges[:j]
|
||||
break
|
||||
# print "DONE"
|
||||
ed1, ed2 = eloop1.edges[0], eloop2.edges[0]
|
||||
|
||||
if not ed1.fake or not ed2.fake:
|
||||
raise "Error"
|
||||
|
||||
# Find the join that isnt flipped (juts like detecting a bow-tie face)
|
||||
a1 = (ed1.co1 - ed2.co1).length + (ed1.co2 - ed2.co2).length
|
||||
a2 = (ed1.co1 - ed2.co2).length + (ed1.co2 - ed2.co1).length
|
||||
|
||||
if a1 > a2:
|
||||
eloop2.reverse()
|
||||
# make the first edge the start edge still
|
||||
eloop2.edges.insert(0, eloop2.edges.pop())
|
||||
|
||||
|
||||
|
||||
|
||||
for loopIdx in range(len(eloop2.edges)):
|
||||
e1 = eloop1.edges[loopIdx]
|
||||
e2 = eloop2.edges[loopIdx]
|
||||
|
||||
# Remember the pairs for fan filling culled edges.
|
||||
e1.match = e2; e2.match = e1
|
||||
|
||||
if not (e1.fake or e2.fake):
|
||||
new_faces.append([e1.v1, e1.v2, e2.v2, e2.v1])
|
||||
|
||||
# FAN FILL MISSING FACES.
|
||||
if CULL_FACES:
|
||||
# Culled edges will be in eloop1.
|
||||
FAN_FILLED_FACES = 0
|
||||
|
||||
contextEdge = eloop1.edges[0] # The larger of teh 2
|
||||
while FAN_FILLED_FACES < CULL_FACES:
|
||||
while contextEdge.next.removed == 0:
|
||||
contextEdge = contextEdge.next
|
||||
|
||||
vertFanPivot = contextEdge.match.v2
|
||||
|
||||
while contextEdge.next.removed == 1:
|
||||
#if not contextEdge.next.fake:
|
||||
new_faces.append([contextEdge.next.v1, contextEdge.next.v2, vertFanPivot])
|
||||
|
||||
# Should we use another var?, this will work for now.
|
||||
contextEdge.next.removed = 1
|
||||
|
||||
contextEdge = contextEdge.next
|
||||
FAN_FILLED_FACES += 1
|
||||
|
||||
# may need to fan fill backwards 1 for non closed loops.
|
||||
|
||||
eloop1.restore() # Add culled back into the list.
|
||||
|
||||
return new_faces
|
||||
|
||||
def main(self, context):
|
||||
global CULL_METHOD
|
||||
|
||||
ob = context.object
|
||||
|
||||
is_editmode = (ob.mode=='EDIT')
|
||||
if is_editmode: bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
if ob == None or ob.type != 'MESH':
|
||||
self.report({'ERROR'}, "No active mesh selected\n")
|
||||
return
|
||||
|
||||
me = ob.data
|
||||
|
||||
time1 = time.time()
|
||||
selEdges = getSelectedEdges(context, me, ob)
|
||||
vertLoops = getVertLoops(selEdges, me) # list of lists of edges.
|
||||
if vertLoops == None:
|
||||
self.report({'ERROR'}, "Selection includes verts that are a part of more then 1 loop\n")
|
||||
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
return
|
||||
# print len(vertLoops)
|
||||
|
||||
|
||||
if len(vertLoops) > 2:
|
||||
choice = PupMenu('Loft '+str(len(vertLoops))+' edge loops%t|loop|segment')
|
||||
if choice == -1:
|
||||
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
return
|
||||
|
||||
elif len(vertLoops) < 2:
|
||||
self.report({'ERROR'}, "No Vertloops found\n")
|
||||
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
return
|
||||
else:
|
||||
choice = 2
|
||||
|
||||
|
||||
# The line below checks if any of the vert loops are differenyt in length.
|
||||
if False in [len(v[0]) == len(vertLoops[0][0]) for v in vertLoops]:
|
||||
#XXX CULL_METHOD = PupMenu('Small to large edge loop distrobution method%t|remove edges evenly|remove smallest edges')
|
||||
#XXX if CULL_METHOD == -1:
|
||||
#XXX if is_editmode: Window.EditMode(1)
|
||||
#XXX return
|
||||
|
||||
CULL_METHOD = 1 # XXX FIXME
|
||||
|
||||
|
||||
|
||||
|
||||
if CULL_METHOD ==1: # RESET CULL_METHOD
|
||||
CULL_METHOD = 0 # shortest
|
||||
else:
|
||||
CULL_METHOD = 1 # even
|
||||
|
||||
|
||||
time1 = time.time()
|
||||
# Convert to special edge data.
|
||||
edgeLoops = []
|
||||
for vloop, closed in vertLoops:
|
||||
edgeLoops.append(edgeLoop(vloop, me, closed))
|
||||
|
||||
|
||||
# VERT LOOP ORDERING CODE
|
||||
# "Build a worm" list - grow from Both ends
|
||||
edgeOrderedList = [edgeLoops.pop()]
|
||||
|
||||
# Find the closest.
|
||||
bestSoFar = BIG_NUM
|
||||
bestIdxSoFar = None
|
||||
for edLoopIdx, edLoop in enumerate(edgeLoops):
|
||||
l =(edgeOrderedList[-1].centre - edLoop.centre).length
|
||||
if l < bestSoFar:
|
||||
bestIdxSoFar = edLoopIdx
|
||||
bestSoFar = l
|
||||
|
||||
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
|
||||
|
||||
# Now we have the 2 closest, append to either end-
|
||||
# Find the closest.
|
||||
while edgeLoops:
|
||||
bestSoFar = BIG_NUM
|
||||
bestIdxSoFar = None
|
||||
first_or_last = 0 # Zero is first
|
||||
for edLoopIdx, edLoop in enumerate(edgeLoops):
|
||||
l1 =(edgeOrderedList[-1].centre - edLoop.centre).length
|
||||
|
||||
if l1 < bestSoFar:
|
||||
bestIdxSoFar = edLoopIdx
|
||||
bestSoFar = l1
|
||||
first_or_last = 1 # last
|
||||
|
||||
l2 =(edgeOrderedList[0].centre - edLoop.centre).length
|
||||
if l2 < bestSoFar:
|
||||
bestIdxSoFar = edLoopIdx
|
||||
bestSoFar = l2
|
||||
first_or_last = 0 # last
|
||||
|
||||
if first_or_last: # add closest Last
|
||||
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
|
||||
else: # Add closest First
|
||||
edgeOrderedList.insert(0, edgeLoops.pop(bestIdxSoFar) ) # First
|
||||
|
||||
faces = []
|
||||
|
||||
for i in range(len(edgeOrderedList)-1):
|
||||
faces.extend( skin2EdgeLoops(edgeOrderedList[i], edgeOrderedList[i+1], me, ob, 0) )
|
||||
if choice == 1 and len(edgeOrderedList) > 2: # Loop
|
||||
faces.extend( skin2EdgeLoops(edgeOrderedList[0], edgeOrderedList[-1], me, ob, 0) )
|
||||
|
||||
# REMOVE SELECTED FACES.
|
||||
MESH_MODE= ob.mode
|
||||
if MESH_MODE == 'EDGE' or MESH_MODE == 'VERTEX': pass
|
||||
elif MESH_MODE == 'FACE':
|
||||
try: me.faces.delete(1, [ f for f in me.faces if f.sel ])
|
||||
except: pass
|
||||
|
||||
if 1: # 2.5
|
||||
mesh_faces_extend(me, faces, ob.active_material_index)
|
||||
me.update(calc_edges=True)
|
||||
else:
|
||||
me.faces.extend(faces, smooth = True)
|
||||
|
||||
print('\nSkin done in %.4f sec.' % (time.time()-time1))
|
||||
|
||||
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
|
||||
class MESH_OT_skin(bpy.types.Operator):
|
||||
'''Bridge face loops.'''
|
||||
|
||||
bl_idname = "mesh.skin"
|
||||
bl_label = "Add Torus"
|
||||
bl_register = True
|
||||
bl_undo = True
|
||||
|
||||
'''
|
||||
loft_method = EnumProperty(attr="loft_method", items=[(), ()], description="", default= True)
|
||||
|
||||
'''
|
||||
|
||||
def execute(self, context):
|
||||
main(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Register the operator
|
||||
bpy.types.register(MESH_OT_skin)
|
||||
|
||||
# Add to a menu
|
||||
bpy.types.VIEW3D_MT_edit_mesh_faces.append((lambda self, context: self.layout.operator("mesh.skin", text="Bridge Faces")))
|
||||
|
||||
if __name__ == "__main__":
|
||||
bpy.ops.mesh.skin()
|
||||
@@ -139,21 +139,6 @@ class SubdivisionSet(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class Retopo(bpy.types.Operator):
|
||||
'''TODO - doc'''
|
||||
|
||||
bl_idname = "object.retopology"
|
||||
bl_label = "Retopology from Grease Pencil"
|
||||
bl_register = True
|
||||
bl_undo = True
|
||||
|
||||
def execute(self, context):
|
||||
import retopo
|
||||
reload(retopo)
|
||||
retopo.main()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ShapeTransfer(bpy.types.Operator):
|
||||
'''Copy the active objects current shape to other selected objects with the same number of verts'''
|
||||
|
||||
@@ -384,6 +369,5 @@ if __name__ == "__main__":
|
||||
|
||||
bpy.types.register(SelectPattern)
|
||||
bpy.types.register(SubdivisionSet)
|
||||
bpy.types.register(Retopo)
|
||||
bpy.types.register(ShapeTransfer)
|
||||
bpy.types.register(JoinUVs)
|
||||
|
||||
@@ -268,7 +268,7 @@ void ED_keymap_mesh(wmKeyConfig *keyconf)
|
||||
|
||||
/* add/remove */
|
||||
WM_keymap_add_item(keymap, "MESH_OT_edge_face_add", FKEY, KM_PRESS, 0, 0);
|
||||
WM_keymap_add_item(keymap, "MESH_OT_skin", FKEY, KM_PRESS, KM_CTRL|KM_ALT, 0); /* python */
|
||||
// WM_keymap_add_item(keymap, "MESH_OT_skin", FKEY, KM_PRESS, KM_CTRL|KM_ALT, 0); /* python, removed */
|
||||
WM_keymap_add_item(keymap, "MESH_OT_duplicate_move", DKEY, KM_PRESS, KM_SHIFT, 0);
|
||||
|
||||
WM_keymap_add_menu(keymap, "INFO_MT_mesh_add", AKEY, KM_PRESS, KM_SHIFT, 0);
|
||||
|
||||
Reference in New Issue
Block a user