Newer
Older
# ########################################
# ##### Relax functions ##################
# ########################################
# create lists with knots and points, all correctly sorted
def relax_calculate_knots(loops):
all_knots = []
all_points = []
for loop, circular in loops:
knots = [[], []]
points = [[], []]
if circular:
extend = [False, True, 0, 1, 0, 1]
extend = [True, False, 0, 1, 1, 2]
else:
extend = [False, False, 0, 1, 1, 2]
extend = [False, False, 0, 1, 1, 2]
for j in range(2):
if extend[j]:
loop = [loop[-1]] + loop + [loop[0]]
for i in range(extend[2 + 2 * j], len(loop), 2):
knots[j].append(loop[i])
for i in range(extend[3 + 2 * j], len(loop), 2):
if loop[i] == loop[-1] and not circular:
continue
if len(points[j]) == 0:
points[j].append(loop[i])
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
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])
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
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()
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
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
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
cubic_strength = FloatProperty(
name="Strength",
description="Higher strength results in more fluid curves",
default=1.0,
soft_min=-3.0,
soft_max=3.0
)
interpolation = EnumProperty(
name="Interpolation mode",
items=(('cubic', "Cubic", "Gives curved results"),
('linear', "Linear", "Basic, fast, straight interpolation")),
3203
3204
3205
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
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
description="Interpolation mode: algorithm used when creating "
"segments",
default='cubic'
)
loft = BoolProperty(
name="Loft",
description="Loft multiple loops, instead of considering them as "
"a multi-input for bridging",
default=False
)
loft_loop = BoolProperty(
name="Loop",
description="Connect the first and the last loop with each other",
default=False
)
min_width = 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 = 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 = BoolProperty(
name="Remove faces",
description="Remove faces that are internal after bridging",
default=True
)
reverse = 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 = IntProperty(
name="Segments",
description="Number of segments used to bridge the gap (0=automatic)",
default=1,
min=0,
soft_max=20
)
twist = 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.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)
if loops:
# calculate new geometry
vertices = []
faces = []
max_vert_index = len(bm.verts) - 1
for i in range(1, len(loops)):
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
bl_idname = "mesh.looptools_circle"
bl_label = "Circle"
bl_description = "Move selected vertices into a circle shape"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3387
3388
3389
3390
3391
3392
3393
3394
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
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
custom_radius = BoolProperty(
name="Radius",
description="Force a custom radius",
default=False
)
fit = 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 = BoolProperty(
name="Flatten",
description="Flatten the circle, instead of projecting it on the mesh",
default=True
)
influence = FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
lock_x = BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
lock_y = BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
lock_z = BoolProperty(name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
radius = FloatProperty(
name="Radius",
description="Custom radius for circle",
default=1.0,
min=0.0,
soft_max=1000.0
)
regular = 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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
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
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)
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
bl_idname = "mesh.looptools_curve"
bl_label = "Curve"
bl_description = "Turn a loop into a smooth curve"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
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
boundaries = BoolProperty(
name="Boundaries",
description="Limit the tool to work within the boundaries of the selected vertices",
default=False
)
influence = FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
interpolation = 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 = BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
lock_y = BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
lock_z = BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
regular = BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the curve",
default=True
)
restriction = 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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
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
bl_idname = "mesh.looptools_flatten"
bl_label = "Flatten"
bl_description = "Flatten vertices on a best-fitting plane"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
influence = FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
lock_x = BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
lock_y = BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
lock_z = BoolProperty(name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
plane = 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 = 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")
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
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'}
bl_idname = "remove.gp"
bl_label = "Remove GP"
bl_description = "Remove all Grease Pencil Strokes"
if context.gpencil_data is not None:
bpy.ops.gpencil.data_unlink()
else:
self.report({'INFO'}, "No Grease Pencil data to Unlink")
return {'CANCELLED'}
bl_idname = "mesh.looptools_gstretch"
bl_label = "Gstretch"
bl_description = "Stretch selected vertices to Grease Pencil stroke"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
conversion = 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 = 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 = 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 = 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 = 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 = BoolProperty(
name="Delete strokes",
description="Remove Grease Pencil strokes if they have been used "
"for Gstretch. WARNING: DOES NOT SUPPORT UNDO",
default=False
)
influence = FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
lock_x = BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
lock_y = BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
lock_z = BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
method = 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
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
col.operator("remove.gp", text="Delete GP Strokes")
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)