Newer
Older
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
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
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
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
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)
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
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
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
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')
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.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
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
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
move_verts(object, bm, mapping, move, -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
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
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')
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.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
move_verts(object, bm, mapping, move, 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
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
influence = bpy.props.FloatProperty(name = "Influence",
description = "Force of the tool",
default = 100.0,
min = 0.0,
max = 100.0,
precision = 1,
subtype = 'PERCENTAGE')
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.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_verts(object, bm, False, move, 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
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
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')
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
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)
elif object.grease_pencil:
strokes = gstretch_get_strokes(object)
else:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
else:
# 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
if object.grease_pencil:
strokes = gstretch_get_strokes(object)
else:
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
if object.grease_pencil:
strokes = gstretch_get_strokes(object)
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
bmesh.update_edit_mesh(object.data, tessface=True,
destructive=True)
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
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
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
@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, "input")
col.prop(self, "iterations")
col.prop(self, "regular")
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("Relax",
object, bm, self.input, False)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
else:
# find loops
derived, bm_mod, loops = get_connected_input(object, bm,
context.scene, self.input)
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
knots, points = relax_calculate_knots(loops)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Relax", object, bm, self.input, False, False, loops,
derived, mapping)
CoDEmanX
committed
for iteration in range(int(self.iterations)):
# calculate splines and new positions
tknots, tpoints = relax_calculate_t(bm_mod, knots, points,
self.regular)
splines = []
for i in range(len(knots)):
splines.append(calculate_splines(self.interpolation, bm_mod,
tknots[i], knots[i]))
move = [relax_calculate_verts(bm_mod, self.interpolation,
tknots, knots, tpoints, points, splines)]
move_verts(object, bm, mapping, move, -1)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
terminate(global_undo)
CoDEmanX
committed
return{'FINISHED'}
# space operator
class Space(bpy.types.Operator):
bl_idname = "mesh.looptools_space"
bl_label = "Space"
bl_description = "Space the vertices in a regular distrubtion on the loop"
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')
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 spaced",
default = 'selected')
interpolation = bpy.props.EnumProperty(name = "Interpolation",
items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Vertices are projected on existing edges")),
description = "Algorithm used for interpolation",
default = 'cubic')
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, "input")
col.separator()
CoDEmanX
committed
col.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("Space",
object, bm, self.input, False)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
else:
# find loops
derived, bm_mod, loops = get_connected_input(object, bm,
context.scene, self.input)
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Space", object, bm, self.input, False, False, loops,
derived, mapping)
CoDEmanX
committed
move = []
for loop in loops:
# calculate splines and new positions
if loop[1]: # circular
loop[0].append(loop[0][0])
tknots, tpoints = space_calculate_t(bm_mod, loop[0][:])
splines = calculate_splines(self.interpolation, bm_mod,
tknots, loop[0][:])
move.append(space_calculate_verts(bm_mod, self.interpolation,
tknots, tpoints, loop[0][:-1], splines))
# move vertices to new locations
move_verts(object, bm, mapping, move, self.influence)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
terminate(global_undo)
CoDEmanX
committed
return{'FINISHED'}
##########################################
####### GUI and registration #############
##########################################
# menu containing all tools
class VIEW3D_MT_edit_mesh_looptools(bpy.types.Menu):
bl_label = "LoopTools"
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
layout.operator("mesh.looptools_bridge", text="Bridge").loft = False
layout.operator("mesh.looptools_circle")
layout.operator("mesh.looptools_curve")
layout.operator("mesh.looptools_flatten")
layout.operator("mesh.looptools_bridge", text="Loft").loft = True
layout.operator("mesh.looptools_relax")
layout.operator("mesh.looptools_space")
# panel containing all tools
class VIEW3D_PT_tools_looptools(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_context = "mesh_edit"
bl_label = "LoopTools"
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
lt = context.window_manager.looptools
CoDEmanX
committed
# bridge - first line
split = col.split(percentage=0.15, align=True)
if lt.display_bridge:
split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT')
else:
split.prop(lt, "display_bridge", text="", icon='RIGHTARROW')
split.operator("mesh.looptools_bridge", text="Bridge").loft = False
# bridge - settings
if lt.display_bridge:
box = col.column(align=True).box().column()
#box.prop(self, "mode")
CoDEmanX
committed
# top row
col_top = box.column(align=True)
row = col_top.row(align=True)
col_left = row.column(align=True)
col_right = row.column(align=True)
col_right.active = lt.bridge_segments != 1
col_left.prop(lt, "bridge_segments")