Newer
Older
elif loop[i] != points[j][0]:
points[j].append(loop[i])
if circular:
if knots[j][0] != knots[j][-1]:
knots[j].append(knots[j][0])
if len(points[1]) == 0:
knots.pop(1)
points.pop(1)
for k in knots:
all_knots.append(k)
for p in points:
all_points.append(p)
CoDEmanX
committed
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
return(all_knots, all_points)
# calculate relative positions compared to first knot
def relax_calculate_t(bm_mod, knots, points, regular):
all_tknots = []
all_tpoints = []
for i in range(len(knots)):
amount = len(knots[i]) + len(points[i])
mix = []
for j in range(amount):
if j%2 == 0:
mix.append([True, knots[i][round(j/2)]])
elif j == amount-1:
mix.append([True, knots[i][-1]])
else:
mix.append([False, points[i][int(j/2)]])
len_total = 0
loc_prev = False
tknots = []
tpoints = []
for m in mix:
loc = mathutils.Vector(bm_mod.verts[m[1]].co[:])
if not loc_prev:
loc_prev = loc
len_total += (loc - loc_prev).length
if m[0]:
tknots.append(len_total)
else:
tpoints.append(len_total)
loc_prev = loc
if regular:
tpoints = []
for p in range(len(points[i])):
tpoints.append((tknots[p] + tknots[p+1]) / 2)
all_tknots.append(tknots)
all_tpoints.append(tpoints)
CoDEmanX
committed
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
return(all_tknots, all_tpoints)
# change the location of the points to their place on the spline
def relax_calculate_verts(bm_mod, interpolation, tknots, knots, tpoints,
points, splines):
change = []
move = []
for i in range(len(knots)):
for p in points[i]:
m = tpoints[i][points[i].index(p)]
if m in tknots[i]:
n = tknots[i].index(m)
else:
t = tknots[i][:]
t.append(m)
t.sort()
n = t.index(m)-1
if n > len(splines[i]) - 1:
n = len(splines[i]) - 1
elif n < 0:
n = 0
CoDEmanX
committed
if interpolation == 'cubic':
ax, bx, cx, dx, tx = splines[i][n][0]
x = ax + bx*(m-tx) + cx*(m-tx)**2 + dx*(m-tx)**3
ay, by, cy, dy, ty = splines[i][n][1]
y = ay + by*(m-ty) + cy*(m-ty)**2 + dy*(m-ty)**3
az, bz, cz, dz, tz = splines[i][n][2]
z = az + bz*(m-tz) + cz*(m-tz)**2 + dz*(m-tz)**3
change.append([p, mathutils.Vector([x,y,z])])
else: # interpolation == 'linear'
a, d, t, u = splines[i][n]
if u == 0:
u = 1e-8
change.append([p, ((m-t)/u)*d + a])
for c in change:
move.append([c[0], (bm_mod.verts[c[0]].co + c[1]) / 2])
CoDEmanX
committed
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
return(move)
##########################################
####### Space functions ##################
##########################################
# calculate relative positions compared to first knot
def space_calculate_t(bm_mod, knots):
tknots = []
loc_prev = False
len_total = 0
for k in knots:
loc = mathutils.Vector(bm_mod.verts[k].co[:])
if not loc_prev:
loc_prev = loc
len_total += (loc - loc_prev).length
tknots.append(len_total)
loc_prev = loc
amount = len(knots)
t_per_segment = len_total / (amount - 1)
tpoints = [i * t_per_segment for i in range(amount)]
CoDEmanX
committed
return(tknots, tpoints)
# change the location of the points to their place on the spline
def space_calculate_verts(bm_mod, interpolation, tknots, tpoints, points,
splines):
move = []
for p in points:
m = tpoints[points.index(p)]
if m in tknots:
n = tknots.index(m)
else:
t = tknots[:]
t.append(m)
t.sort()
n = t.index(m) - 1
if n > len(splines) - 1:
n = len(splines) - 1
elif n < 0:
n = 0
CoDEmanX
committed
if interpolation == 'cubic':
ax, bx, cx, dx, tx = splines[n][0]
x = ax + bx*(m-tx) + cx*(m-tx)**2 + dx*(m-tx)**3
ay, by, cy, dy, ty = splines[n][1]
y = ay + by*(m-ty) + cy*(m-ty)**2 + dy*(m-ty)**3
az, bz, cz, dz, tz = splines[n][2]
z = az + bz*(m-tz) + cz*(m-tz)**2 + dz*(m-tz)**3
move.append([p, mathutils.Vector([x,y,z])])
else: # interpolation == 'linear'
a, d, t, u = splines[n]
move.append([p, ((m-t)/u)*d + a])
CoDEmanX
committed
return(move)
##########################################
####### Operators ########################
##########################################
# bridge operator
class Bridge(bpy.types.Operator):
bl_idname = 'mesh.looptools_bridge'
bl_label = "Bridge / Loft"
bl_description = "Bridge two, or loft several, loops of vertices"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
cubic_strength = bpy.props.FloatProperty(name = "Strength",
description = "Higher strength results in more fluid curves",
default = 1.0,
soft_min = -3.0,
soft_max = 3.0)
interpolation = bpy.props.EnumProperty(name = "Interpolation mode",
items = (('cubic', "Cubic", "Gives curved results"),
('linear', "Linear", "Basic, fast, straight interpolation")),
description = "Interpolation mode: algorithm used when creating "\
"segments",
default = 'cubic')
loft = bpy.props.BoolProperty(name = "Loft",
description = "Loft multiple loops, instead of considering them as "\
"a multi-input for bridging",
default = False)
loft_loop = bpy.props.BoolProperty(name = "Loop",
description = "Connect the first and the last loop with each other",
default = False)
min_width = bpy.props.IntProperty(name = "Minimum width",
description = "Segments with an edge smaller than this are merged "\
"(compared to base edge)",
default = 0,
min = 0,
max = 100,
subtype = 'PERCENTAGE')
mode = bpy.props.EnumProperty(name = "Mode",
items = (('basic', "Basic", "Fast algorithm"), ('shortest',
"Shortest edge", "Slower algorithm with better vertex matching")),
description = "Algorithm used for bridging",
default = 'shortest')
remove_faces = bpy.props.BoolProperty(name = "Remove faces",
description = "Remove faces that are internal after bridging",
default = True)
reverse = bpy.props.BoolProperty(name = "Reverse",
description = "Manually override the direction in which the loops "\
"are bridged. Only use if the tool gives the wrong " \
"result",
default = False)
segments = bpy.props.IntProperty(name = "Segments",
description = "Number of segments used to bridge the gap "\
"(0 = automatic)",
default = 1,
min = 0,
soft_max = 20)
twist = bpy.props.IntProperty(name = "Twist",
description = "Twist what vertices are connected to each other",
default = 0)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
#layout.prop(self, "mode") # no cases yet where 'basic' mode is needed
CoDEmanX
committed
# top row
col_top = layout.column(align=True)
row = col_top.row(align=True)
col_left = row.column(align=True)
col_right = row.column(align=True)
col_right.active = self.segments != 1
col_left.prop(self, "segments")
col_right.prop(self, "min_width", text="")
# bottom row
bottom_left = col_left.row()
bottom_left.active = self.segments != 1
bottom_left.prop(self, "interpolation", text="")
bottom_right = col_right.row()
bottom_right.active = self.interpolation == 'cubic'
bottom_right.prop(self, "cubic_strength")
# boolean properties
col_top.prop(self, "remove_faces")
if self.loft:
col_top.prop(self, "loft_loop")
CoDEmanX
committed
# override properties
col_top.separator()
row = layout.row(align = True)
row.prop(self, "twist")
row.prop(self, "reverse")
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
context.window_manager.looptools.bridge_loft = self.loft
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
global_undo, object, bm = initialise()
edge_faces, edgekey_to_edge, old_selected_faces, smooth = \
bridge_initialise(bm, self.interpolation)
settings_write(self)
CoDEmanX
committed
# check cache to see if we can save time
input_method = bridge_input_method(self.loft, self.loft_loop)
cached, single_loops, loops, derived, mapping = cache_read("Bridge",
object, bm, input_method, False)
if not cached:
# get loops
loops = bridge_get_input(bm)
if loops:
# reorder loops if there are more than 2
if len(loops) > 2:
if self.loft:
loops = bridge_sort_loops(bm, loops, self.loft_loop)
else:
loops = bridge_match_loops(bm, loops)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Bridge", object, bm, input_method, False, False,
loops, False, False)
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
if loops:
# calculate new geometry
vertices = []
faces = []
max_vert_index = len(bm.verts)-1
for i in range(1, len(loops)):
if not self.loft and i%2 == 0:
continue
lines = bridge_calculate_lines(bm, loops[i-1:i+1],
self.mode, self.twist, self.reverse)
vertex_normals = bridge_calculate_virtual_vertex_normals(bm,
lines, loops[i-1:i+1], edge_faces, edgekey_to_edge)
segments = bridge_calculate_segments(bm, lines,
loops[i-1:i+1], self.segments)
new_verts, new_faces, max_vert_index = \
bridge_calculate_geometry(bm, lines, vertex_normals,
segments, self.interpolation, self.cubic_strength,
self.min_width, max_vert_index)
if new_verts:
vertices += new_verts
if new_faces:
faces += new_faces
# make sure faces in loops that aren't used, aren't removed
if self.remove_faces and old_selected_faces:
bridge_save_unused_faces(bm, old_selected_faces, loops)
# create vertices
if vertices:
bridge_create_vertices(bm, vertices)
# create faces
if faces:
new_faces = bridge_create_faces(object, bm, faces, self.twist)
old_selected_faces = [i for i, face in enumerate(bm.faces) \
if face.index in old_selected_faces] # updating list
bridge_select_new_faces(new_faces, smooth)
# edge-data could have changed, can't use cache next run
if faces and not vertices:
cache_delete("Bridge")
# delete internal faces
if self.remove_faces and old_selected_faces:
bridge_remove_internal_faces(bm, old_selected_faces)
# make sure normals are facing outside
bmesh.update_edit_mesh(object.data, tessface=False,
destructive=True)
bpy.ops.mesh.normals_make_consistent()
CoDEmanX
committed
# cleaning up
terminate(global_undo)
CoDEmanX
committed
return{'FINISHED'}
# circle operator
class Circle(bpy.types.Operator):
bl_idname = "mesh.looptools_circle"
bl_label = "Circle"
bl_description = "Move selected vertices into a circle shape"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
custom_radius = bpy.props.BoolProperty(name = "Radius",
description = "Force a custom radius",
default = False)
fit = bpy.props.EnumProperty(name = "Method",
items = (("best", "Best fit", "Non-linear least squares"),
("inside", "Fit inside","Only move vertices towards the center")),
description = "Method used for fitting a circle to the vertices",
default = 'best')
flatten = bpy.props.BoolProperty(name = "Flatten",
description = "Flatten the circle, instead of projecting it on the " \
"mesh",
default = True)
influence = bpy.props.FloatProperty(name = "Influence",
description = "Force of the tool",
default = 100.0,
min = 0.0,
max = 100.0,
precision = 1,
subtype = 'PERCENTAGE')
lock_x = bpy.props.BoolProperty(name = "Lock X",
description = "Lock editing of the x-coordinate",
default = False)
lock_y = bpy.props.BoolProperty(name = "Lock Y",
description = "Lock editing of the y-coordinate",
default = False)
lock_z = bpy.props.BoolProperty(name = "Lock Z",
description = "Lock editing of the z-coordinate",
default = False)
radius = bpy.props.FloatProperty(name = "Radius",
description = "Custom radius for circle",
default = 1.0,
min = 0.0,
soft_max = 1000.0)
regular = bpy.props.BoolProperty(name = "Regular",
description = "Distribute vertices at constant distances along the " \
"circle",
default = True)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "fit")
col.separator()
CoDEmanX
committed
col.prop(self, "flatten")
row = col.row(align=True)
row.prop(self, "custom_radius")
row_right = row.row(align=True)
row_right.active = self.custom_radius
row_right.prop(self, "radius", text="")
col.prop(self, "regular")
col.separator()
CoDEmanX
committed
col_move = col.column(align=True)
row = col_move.row(align=True)
if self.lock_x:
row.prop(self, "lock_x", text = "X", icon='LOCKED')
else:
row.prop(self, "lock_x", text = "X", icon='UNLOCKED')
if self.lock_y:
row.prop(self, "lock_y", text = "Y", icon='LOCKED')
else:
row.prop(self, "lock_y", text = "Y", icon='UNLOCKED')
if self.lock_z:
row.prop(self, "lock_z", text = "Z", icon='LOCKED')
else:
row.prop(self, "lock_z", text = "Z", icon='UNLOCKED')
col_move.prop(self, "influence")
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
global_undo, object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Circle",
object, bm, False, False)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
else:
# find loops
derived, bm_mod, single_vertices, single_loops, loops = \
circle_get_input(object, bm, context.scene)
mapping = get_mapping(derived, bm, bm_mod, single_vertices,
False, loops)
single_loops, loops = circle_check_loops(single_loops, loops,
mapping, bm_mod)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Circle", object, bm, False, False, single_loops,
loops, derived, mapping)
CoDEmanX
committed
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
move = []
for i, loop in enumerate(loops):
# best fitting flat plane
com, normal = calculate_plane(bm_mod, loop)
# if circular, shift loop so we get a good starting vertex
if loop[1]:
loop = circle_shift_loop(bm_mod, loop, com)
# flatten vertices on plane
locs_2d, p, q = circle_3d_to_2d(bm_mod, loop, com, normal)
# calculate circle
if self.fit == 'best':
x0, y0, r = circle_calculate_best_fit(locs_2d)
else: # self.fit == 'inside'
x0, y0, r = circle_calculate_min_fit(locs_2d)
# radius override
if self.custom_radius:
r = self.radius / p.length
# calculate positions on circle
if self.regular:
new_locs_2d = circle_project_regular(locs_2d[:], x0, y0, r)
else:
new_locs_2d = circle_project_non_regular(locs_2d[:], x0, y0, r)
# take influence into account
locs_2d = circle_influence_locs(locs_2d, new_locs_2d,
self.influence)
# calculate 3d positions of the created 2d input
move.append(circle_calculate_verts(self.flatten, bm_mod,
locs_2d, com, p, q, normal))
# flatten single input vertices on plane defined by loop
if self.flatten and single_loops:
move.append(circle_flatten_singles(bm_mod, com, p, q,
normal, single_loops[i]))
CoDEmanX
committed
# move vertices to new locations
if self.lock_x or self.lock_y or self.lock_z:
lock = [self.lock_x, self.lock_y, self.lock_z]
else:
lock = False
move_verts(object, bm, mapping, move, lock, -1)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
terminate(global_undo)
CoDEmanX
committed
return{'FINISHED'}
# curve operator
class Curve(bpy.types.Operator):
bl_idname = "mesh.looptools_curve"
bl_label = "Curve"
bl_description = "Turn a loop into a smooth curve"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
boundaries = bpy.props.BoolProperty(name = "Boundaries",
description = "Limit the tool to work within the boundaries of the "\
"selected vertices",
default = False)
influence = bpy.props.FloatProperty(name = "Influence",
description = "Force of the tool",
default = 100.0,
min = 0.0,
max = 100.0,
precision = 1,
subtype = 'PERCENTAGE')
interpolation = bpy.props.EnumProperty(name = "Interpolation",
items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description = "Algorithm used for interpolation",
default = 'cubic')
lock_x = bpy.props.BoolProperty(name = "Lock X",
description = "Lock editing of the x-coordinate",
default = False)
lock_y = bpy.props.BoolProperty(name = "Lock Y",
description = "Lock editing of the y-coordinate",
default = False)
lock_z = bpy.props.BoolProperty(name = "Lock Z",
description = "Lock editing of the z-coordinate",
default = False)
regular = bpy.props.BoolProperty(name = "Regular",
description = "Distribute vertices at constant distances along the" \
"curve",
default = True)
restriction = bpy.props.EnumProperty(name = "Restriction",
items = (("none", "None", "No restrictions on vertex movement"),
("extrude", "Extrude only","Only allow extrusions (no "\
"indentations)"),
("indent", "Indent only", "Only allow indentation (no "\
"extrusions)")),
description = "Restrictions on how the vertices can be moved",
default = 'none')
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "interpolation")
col.prop(self, "restriction")
col.prop(self, "boundaries")
col.prop(self, "regular")
col.separator()
CoDEmanX
committed
col_move = col.column(align=True)
row = col_move.row(align=True)
if self.lock_x:
row.prop(self, "lock_x", text = "X", icon='LOCKED')
else:
row.prop(self, "lock_x", text = "X", icon='UNLOCKED')
if self.lock_y:
row.prop(self, "lock_y", text = "Y", icon='LOCKED')
else:
row.prop(self, "lock_y", text = "Y", icon='UNLOCKED')
if self.lock_z:
row.prop(self, "lock_z", text = "Z", icon='LOCKED')
else:
row.prop(self, "lock_z", text = "Z", icon='UNLOCKED')
col_move.prop(self, "influence")
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
global_undo, object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Curve",
object, bm, False, self.boundaries)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
else:
# find loops
derived, bm_mod, loops = curve_get_input(object, bm,
self.boundaries, context.scene)
mapping = get_mapping(derived, bm, bm_mod, False, True, loops)
loops = check_loops(loops, mapping, bm_mod)
verts_selected = [v.index for v in bm_mod.verts if v.select \
and not v.hide]
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Curve", object, bm, False, self.boundaries, False,
loops, derived, mapping)
CoDEmanX
committed
move = []
for loop in loops:
knots, points = curve_calculate_knots(loop, verts_selected)
pknots = curve_project_knots(bm_mod, verts_selected, knots,
points, loop[1])
tknots, tpoints = curve_calculate_t(bm_mod, knots, points,
pknots, self.regular, loop[1])
splines = calculate_splines(self.interpolation, bm_mod,
tknots, knots)
move.append(curve_calculate_vertices(bm_mod, knots, tknots,
points, tpoints, splines, self.interpolation,
self.restriction))
CoDEmanX
committed
# move vertices to new locations
if self.lock_x or self.lock_y or self.lock_z:
lock = [self.lock_x, self.lock_y, self.lock_z]
else:
lock = False
move_verts(object, bm, mapping, move, lock, self.influence)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
terminate(global_undo)
return{'FINISHED'}
# flatten operator
class Flatten(bpy.types.Operator):
bl_idname = "mesh.looptools_flatten"
bl_label = "Flatten"
bl_description = "Flatten vertices on a best-fitting plane"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
influence = bpy.props.FloatProperty(name = "Influence",
description = "Force of the tool",
default = 100.0,
min = 0.0,
max = 100.0,
precision = 1,
subtype = 'PERCENTAGE')
lock_x = bpy.props.BoolProperty(name = "Lock X",
description = "Lock editing of the x-coordinate",
default = False)
lock_y = bpy.props.BoolProperty(name = "Lock Y",
description = "Lock editing of the y-coordinate",
default = False)
lock_z = bpy.props.BoolProperty(name = "Lock Z",
description = "Lock editing of the z-coordinate",
default = False)
plane = bpy.props.EnumProperty(name = "Plane",
items = (("best_fit", "Best fit", "Calculate a best fitting plane"),
("normal", "Normal", "Derive plane from averaging vertex "\
"normals"),
("view", "View", "Flatten on a plane perpendicular to the "\
"viewing angle")),
description = "Plane on which vertices are flattened",
default = 'best_fit')
restriction = bpy.props.EnumProperty(name = "Restriction",
items = (("none", "None", "No restrictions on vertex movement"),
("bounding_box", "Bounding box", "Vertices are restricted to "\
"movement inside the bounding box of the selection")),
description = "Restrictions on how the vertices can be moved",
default = 'none')
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "plane")
#col.prop(self, "restriction")
col.separator()
CoDEmanX
committed
col_move = col.column(align=True)
row = col_move.row(align=True)
if self.lock_x:
row.prop(self, "lock_x", text = "X", icon='LOCKED')
else:
row.prop(self, "lock_x", text = "X", icon='UNLOCKED')
if self.lock_y:
row.prop(self, "lock_y", text = "Y", icon='LOCKED')
else:
row.prop(self, "lock_y", text = "Y", icon='UNLOCKED')
if self.lock_z:
row.prop(self, "lock_z", text = "Z", icon='LOCKED')
else:
row.prop(self, "lock_z", text = "Z", icon='UNLOCKED')
col_move.prop(self, "influence")
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
global_undo, object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Flatten",
object, bm, False, False)
if not cached:
# order input into virtual loops
loops = flatten_get_input(bm)
loops = check_loops(loops, mapping, bm)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Flatten", object, bm, False, False, False, loops,
False, False)
CoDEmanX
committed
move = []
for loop in loops:
# calculate plane and position of vertices on them
com, normal = calculate_plane(bm, loop, method=self.plane,
object=object)
to_move = flatten_project(bm, loop, com, normal)
if self.restriction == 'none':
move.append(to_move)
else:
move.append(to_move)
# move vertices to new locations
if self.lock_x or self.lock_y or self.lock_z:
lock = [self.lock_x, self.lock_y, self.lock_z]
else:
lock = False
move_verts(object, bm, False, move, lock, self.influence)
CoDEmanX
committed
# cleaning up
terminate(global_undo)
CoDEmanX
committed
return{'FINISHED'}
# gstretch operator
class GStretch(bpy.types.Operator):
bl_idname = "mesh.looptools_gstretch"
bl_label = "Gstretch"
bl_description = "Stretch selected vertices to Grease Pencil stroke"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
conversion = bpy.props.EnumProperty(name = "Conversion",
items = (("distance", "Distance", "Set the distance between vertices "\
"of the converted grease pencil stroke"),
("limit_vertices", "Limit vertices", "Set the minimum and maximum "\
"number of vertices that converted GP strokes will have"),
("vertices", "Exact vertices", "Set the exact number of vertices "\
"that converted grease pencil strokes will have. Short strokes "\
"with few points may contain less vertices than this number."),
("none", "No simplification", "Convert each grease pencil point "\
"to a vertex")),
description = "If grease pencil strokes are converted to geometry, "\
"use this simplification method",
default = 'limit_vertices')
conversion_distance = bpy.props.FloatProperty(name = "Distance",
description = "Absolute distance between vertices along the converted "\
"grease pencil stroke",
default = 0.1,
min = 0.000001,
soft_min = 0.01,
soft_max = 100)
conversion_max = bpy.props.IntProperty(name = "Max Vertices",
description = "Maximum number of vertices grease pencil strokes will "\
"have, when they are converted to geomtery",
default = 32,
min = 3,
soft_max = 500,
update = gstretch_update_min)
conversion_min = bpy.props.IntProperty(name = "Min Vertices",
description = "Minimum number of vertices grease pencil strokes will "\
"have, when they are converted to geomtery",
default = 8,
min = 3,
soft_max = 500,
update = gstretch_update_max)
conversion_vertices = bpy.props.IntProperty(name = "Vertices",
description = "Number of vertices grease pencil strokes will "\
"have, when they are converted to geometry. If strokes have less "\
"points than required, the 'Spread evenly' method is used",
default = 32,
min = 3,
soft_max = 500)
delete_strokes = bpy.props.BoolProperty(name="Delete strokes",
description = "Remove Grease Pencil strokes if they have been used "\
"for Gstretch. WARNING: DOES NOT SUPPORT UNDO",
CoDEmanX
committed
default = False)
influence = bpy.props.FloatProperty(name = "Influence",
description = "Force of the tool",
default = 100.0,
min = 0.0,
max = 100.0,
precision = 1,
subtype = 'PERCENTAGE')
lock_x = bpy.props.BoolProperty(name = "Lock X",
description = "Lock editing of the x-coordinate",
default = False)
lock_y = bpy.props.BoolProperty(name = "Lock Y",
description = "Lock editing of the y-coordinate",
default = False)
lock_z = bpy.props.BoolProperty(name = "Lock Z",
description = "Lock editing of the z-coordinate",
default = False)
method = bpy.props.EnumProperty(name = "Method",
items = (("project", "Project", "Project vertices onto the stroke, "\
"using vertex normals and connected edges"),
("irregular", "Spread", "Distribute vertices along the full "\
"stroke, retaining relative distances between the vertices"),
("regular", "Spread evenly", "Distribute vertices at regular "\
"distances along the full stroke")),
description = "Method of distributing the vertices over the Grease "\
"Pencil stroke",
default = 'regular')
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "delete_strokes")
col.separator()
CoDEmanX
committed
col_conv = col.column(align=True)
col_conv.prop(self, "conversion", text="")
if self.conversion == 'distance':
col_conv.prop(self, "conversion_distance")
elif self.conversion == 'limit_vertices':
row = col_conv.row(align=True)
row.prop(self, "conversion_min", text="Min")
row.prop(self, "conversion_max", text="Max")
elif self.conversion == 'vertices':
col_conv.prop(self, "conversion_vertices")
CoDEmanX
committed
col_move = col.column(align=True)
row = col_move.row(align=True)
if self.lock_x:
row.prop(self, "lock_x", text = "X", icon='LOCKED')
else:
row.prop(self, "lock_x", text = "X", icon='UNLOCKED')
if self.lock_y:
row.prop(self, "lock_y", text = "Y", icon='LOCKED')
else:
row.prop(self, "lock_y", text = "Y", icon='UNLOCKED')
if self.lock_z:
row.prop(self, "lock_z", text = "Z", icon='LOCKED')
else:
row.prop(self, "lock_z", text = "Z", icon='UNLOCKED')
col_move.prop(self, "influence")
CoDEmanX
committed
# flush cached strokes
if 'Gstretch' in looptools_cache:
looptools_cache['Gstretch']['single_loops'] = []
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
global_undo, object, bm = initialise()
settings_write(self)
CoDEmanX
committed
cached, safe_strokes, loops, derived, mapping = cache_read("Gstretch",
if safe_strokes:
strokes = gstretch_safe_to_true_strokes(safe_strokes)
# cached strokes were flushed (see operator's invoke function)
beta-tester
committed
elif get_grease_pencil(object, context):
strokes = gstretch_get_strokes(object, context)
# straightening function (no GP) -> loops ignore modifiers
straightening = True
derived = False
bm_mod = bm.copy()
beta-tester
committed
bm_mod.verts.ensure_lookup_table() ### 2.73
bm_mod.edges.ensure_lookup_table() ### 2.73
bm_mod.faces.ensure_lookup_table() ### 2.73
strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
if not straightening:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
beta-tester
committed
if get_grease_pencil(object, context):
# find loops
derived, bm_mod, loops = get_connected_input(object, bm,
context.scene, input='selected')
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
# get strokes
beta-tester
committed
strokes = gstretch_get_strokes(object, context)
# straightening function (no GP) -> loops ignore modifiers
derived = False
mapping = False
bm_mod = bm.copy()
beta-tester
committed
bm_mod.verts.ensure_lookup_table() ### 2.73
bm_mod.edges.ensure_lookup_table() ### 2.73
bm_mod.faces.ensure_lookup_table() ### 2.73
edge_keys = [edgekey(edge) for edge in bm_mod.edges if \
edge.select and not edge.hide]
loops = get_connected_selections(edge_keys)
loops = check_loops(loops, mapping, bm_mod)
# create fake strokes
strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
if strokes:
safe_strokes = gstretch_true_to_safe_strokes(strokes)
else:
safe_strokes = []
cache_write("Gstretch", object, bm, False, False,
safe_strokes, loops, derived, mapping)
# pair loops and strokes
ls_pairs = gstretch_match_loops_strokes(loops, strokes, object, bm_mod)
ls_pairs = gstretch_align_pairs(ls_pairs, object, bm_mod, self.method)
CoDEmanX
committed
if not loops:
# no selected geometry, convert GP to verts
if strokes:
move.append(gstretch_create_verts(object, bm, strokes,
self.method, self.conversion, self.conversion_distance,
self.conversion_max, self.conversion_min,
self.conversion_vertices))
for stroke in strokes:
gstretch_erase_stroke(stroke, context)
elif ls_pairs:
for (loop, stroke) in ls_pairs:
move.append(gstretch_calculate_verts(loop, stroke, object,
bm_mod, self.method))
if self.delete_strokes:
if type(stroke) != bpy.types.GPencilStroke:
# in case of cached fake stroke, get the real one
beta-tester
committed
if get_grease_pencil(object, context):
strokes = gstretch_get_strokes(object, context)
if loops and strokes:
ls_pairs = gstretch_match_loops_strokes(loops,
strokes, object, bm_mod)
ls_pairs = gstretch_align_pairs(ls_pairs,
object, bm_mod, self.method)
for (l, s) in ls_pairs:
if l == loop:
stroke = s
break
if self.lock_x or self.lock_y or self.lock_z:
lock = [self.lock_x, self.lock_y, self.lock_z]
else:
lock = False
bmesh.update_edit_mesh(object.data, tessface=True, destructive=True)
move_verts(object, bm, mapping, move, lock, self.influence)
CoDEmanX
committed
# cleaning up
CoDEmanX
committed
# relax operator
class Relax(bpy.types.Operator):
bl_idname = "mesh.looptools_relax"
bl_label = "Relax"
bl_description = "Relax the loop, so it is smoother"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
input = bpy.props.EnumProperty(name = "Input",
items = (("all", "Parallel (all)", "Also use non-selected "\
"parallel loops as input"),
("selected", "Selection","Only use selected vertices as input")),
description = "Loops that are relaxed",
default = 'selected')
interpolation = bpy.props.EnumProperty(name = "Interpolation",
items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description = "Algorithm used for interpolation",
default = 'cubic')
iterations = bpy.props.EnumProperty(name = "Iterations",
items = (("1", "1", "One"),
("3", "3", "Three"),
("5", "5", "Five"),
("10", "10", "Ten"),
("25", "25", "Twenty-five")),
description = "Number of times the loop is relaxed",
default = "1")
regular = bpy.props.BoolProperty(name = "Regular",
description = "Distribute vertices at constant distances along the" \
"loop",
default = True)
CoDEmanX
committed