Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
# TO DO LIST #
# Add more options to curve radius/modulation plus cyclic/connect curve option
from bpy.types import Operator
from random import (
choice as rand_choice,
random as rand_random,
randint as rand_randint,
uniform as rand_uniform,
)
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# Selection Module: Contributors: Mackraken, Andrew Hale (TrumanBlending)
# Adapted from Mackraken's "Tools for Curves" addon
selected = []
class SelectionOrder(bpy.types.Operator):
"""Store the object names in the order they are selected, """ \
"""use RETURN key to confirm selection, ESCAPE key to cancel"""
bl_idname = "object.select_order"
bl_label = "Select with Order"
bl_options = {'UNDO'}
num_selected = 0
@classmethod
def poll(self, context):
return bpy.context.mode == 'OBJECT'
def update(self, context):
# Get the currently selected objects
sel = context.selected_objects
num = len(sel)
if num == 0:
# Reset the list
del selected[:]
elif num > self.num_selected:
# Get all the newly selected objects and add
new = [ob.name for ob in sel if ob.name not in selected]
selected.extend(new)
elif num < self.num_selected:
# Get the selected objects and remove from list
curnames = {ob.name for ob in sel}
selected[:] = [name for name in selected if name in curnames]
# Set the number of currently select objects
self.num_selected = len(selected)
def modal(self, context, event):
if event.type == 'RET':
# If return is pressed, finish the operator
return {'FINISHED'}
elif event.type == 'ESC':
# If escape is pressed, cancel the operator
return {'CANCELLED'}
# Update selection if we need to
self.update(context)
return {'PASS_THROUGH'}
def invoke(self, context, event):
self.update(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def error_handlers(self, op_name, error, reports="ERROR", func=False):
if self and reports:
self.report({'WARNING'}, reports + " (See Console for more info)")
is_func = "Function" if func else "Operator"
print("\n[Btrace]\n{}: {}\nError: {}\n".format(op_name, is_func, error))
# Object Trace
# creates a curve with a modulated radius connecting points of a mesh
class OBJECT_OT_objecttrace(Operator):
bl_idname = "object.btobjecttrace"
bl_label = "Btrace: Object Trace"
bl_description = ("Trace selected mesh object with a curve with the option to animate\n"
"The Active Object has to be of a Mesh or Font type")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object and
context.object.type in {'MESH', 'FONT'})
try:
# Run through each selected object and convert to to a curved object
brushObj = context.selected_objects
Btrace = context.window_manager.curve_tracer
check_materials = True
# Duplicate Mesh
if Btrace.object_duplicate:
bpy.ops.object.duplicate_move()
brushObj = context.selected_objects
# Join Mesh
if Btrace.convert_joinbefore:
if len(brushObj) > 1: # Only run if multiple objects selected
bpy.ops.object.join()
brushObj = context.selected_objects
for i in brushObj:
context.view_layer.objects.active = i
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
if i and i.type != 'CURVE':
bpy.ops.object.btconvertcurve()
# Materials
trace_mats = addtracemat(bpy.context.object.data)
if not trace_mats and check_materials is True:
check_materials = False
if Btrace.animate:
bpy.ops.curve.btgrow()
if check_materials is False:
self.report({'WARNING'}, "Some Materials could not be added")
return {'FINISHED'}
except Exception as e:
error_handlers(self, "object.btobjecttrace", e,
"Object Trace could not be completed")
return {'CANCELLED'}
# Objects Connect
# connect selected objects with a curve + hooks to each node
# possible handle types: 'FREE' 'AUTO' 'VECTOR' 'ALIGNED'
class OBJECT_OT_objectconnect(Operator):
bl_idname = "object.btobjectsconnect"
bl_label = "Btrace: Objects Connect"
bl_description = ("Connect selected objects with a curve and add hooks to each node\n"
"Needs at least two objects selected")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 1
try:
lists = []
Btrace = context.window_manager.curve_tracer
curve_handle = Btrace.curve_handle
if curve_handle == 'AUTOMATIC': # hackish because of naming conflict in api
curve_handle = 'AUTO'
# Check if Btrace group exists, if not create
bcollection = bpy.data.collections.keys()
if 'Btrace' not in bcollection:
mycol=bpy.data.collections.new(name="Btrace")
bpy.context.scene.collection.children.link(mycol)
# check if noise
if Btrace.connect_noise:
bpy.ops.object.btfcnoise()
# check if respect order is checked, create list of objects
if Btrace.respect_order is True:
selobnames = selection_utils.selected
obnames = []
for ob in selobnames:
obnames.append(bpy.data.objects[ob])
else:
obnames = bpy.context.selected_objects # No selection order
for a in obnames:
lists.append(a)
# trace the origins
tracer = bpy.data.curves.new('tracer', 'CURVE')
tracer.dimensions = '3D'
spline = tracer.splines.new('BEZIER')
spline.bezier_points.add(len(lists) - 1)
curve = bpy.data.objects.new('curve', tracer)
bpy.context.collection.objects.link(curve)
# render ready curve
tracer.resolution_u = Btrace.curve_u
# Set bevel resolution from Panel options
tracer.bevel_resolution = Btrace.curve_resolution
tracer.fill_mode = 'FULL'
# Set bevel depth from Panel options
tracer.bevel_depth = Btrace.curve_depth
# move nodes to objects
for i in range(len(lists)):
p = spline.bezier_points[i]
p.co = lists[i].location
p.handle_right_type = curve_handle
p.handle_left_type = curve_handle
bpy.context.view_layer.objects.active = curve
# place hooks
for i in range(len(lists)):
curve.data.splines[0].bezier_points[i].select_control_point = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.hook_add_selob()
bpy.ops.object.mode_set(mode='OBJECT')
curve.data.splines[0].bezier_points[i].select_control_point = False
bpy.ops.object.select_all(action='DESELECT')
curve.select_set(True) # selected curve after it's created
# Materials
check_materials = True
trace_mats = addtracemat(bpy.context.object.data)
if not trace_mats and check_materials is True:
check_materials = False
if Btrace.animate: # Add Curve Grow it?
bpy.ops.curve.btgrow()
bpy.data.collections["Btrace"].objects.link(curve) # add to Btrace collection
if Btrace.animate:
bpy.ops.curve.btgrow() # Add grow curve
if check_materials is False:
self.report({'WARNING'}, "Some Materials could not be added")
return {'FINISHED'}
except Exception as e:
error_handlers(self, "object.btobjectsconnect", e,
"Objects Connect could not be completed")
return {'CANCELLED'}
# Particle Trace
# creates a curve from each particle of a system
def curvetracer(curvename, splinename):
Btrace = bpy.context.window_manager.curve_tracer
tracer = bpy.data.curves.new(splinename, 'CURVE')
tracer.dimensions = '3D'
curve = bpy.data.objects.new(curvename, tracer)
bpy.context.collection.objects.link(curve)
try:
tracer.fill_mode = 'FULL'
except:
tracer.use_fill_front = tracer.use_fill_back = False
tracer.bevel_resolution = Btrace.curve_resolution
tracer.bevel_depth = Btrace.curve_depth
tracer.resolution_u = Btrace.curve_u
return tracer, curve
class OBJECT_OT_particletrace(Operator):
bl_idname = "particles.particletrace"
bl_label = "Btrace: Particle Trace"
bl_description = ("Creates a curve from each particle of a system.\n"
"Keeping particle amount under 250 will make this run faster")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.particle_systems)
Btrace = bpy.context.window_manager.curve_tracer
particle_step = Btrace.particle_step # step size in frames
obj = bpy.context.object
obj = bpy.context.evaluated_depsgraph_get().objects.get(obj.name, None)
ps = obj.particle_systems.active
curvelist = []
curve_handle = Btrace.curve_handle
check_materials = True
if curve_handle == 'AUTOMATIC': # hackish naming conflict
curve_handle = 'AUTO'
if curve_handle == 'FREE_ALIGN': # hackish naming conflict
curve_handle = 'FREE'
# Check if Btrace group exists, if not create
bcollection = bpy.data.collections.keys()
if 'Btrace' not in bcollection:
mycol=bpy.data.collections.new(name="Btrace")
bpy.context.scene.collection.children.link(mycol)
if Btrace.curve_join:
tracer = curvetracer('Tracer', 'Splines')
for x in ps.particles:
if not Btrace.curve_join:
tracer = curvetracer('Tracer.000', 'Spline.000')
spline = tracer[0].splines.new('BEZIER')
# add point to spline based on step size
spline.bezier_points.add(int((x.lifetime - 1) // particle_step))
for t in list(range(int(x.lifetime))):
bpy.context.scene.frame_set(int(t + x.birth_time))
if not t % particle_step:
p = spline.bezier_points[t // particle_step]
p.co = x.location
p.handle_right_type = curve_handle
p.handle_left_type = curve_handle
particlecurve = tracer[1]
curvelist.append(particlecurve)
# add to group
bpy.ops.object.select_all(action='DESELECT')
for curveobject in curvelist:
bpy.context.view_layer.objects.active = curveobject
bpy.data.collections["Btrace"].objects.link(curveobject)
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# Materials
trace_mats = addtracemat(curveobject.data)
if not trace_mats and check_materials is True:
check_materials = False
if Btrace.animate:
bpy.ops.curve.btgrow() # Add grow curve
if check_materials is False:
self.report({'WARNING'}, "Some Materials could not be added")
return {'FINISHED'}
except Exception as e:
error_handlers(self, "particles.particletrace", e,
"Particle Trace could not be completed")
return {'CANCELLED'}
# Particle Connect
# connect all particles in active system with a continuous animated curve
class OBJECT_OT_traceallparticles(Operator):
bl_idname = "particles.connect"
bl_label = "Connect Particles"
bl_description = ("Create a continuous animated curve from particles in active system\n"
"Needs an Object with a particle system attached")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.particle_systems)
obj = bpy.context.evaluated_depsgraph_get().objects.get(obj.name, None)
ps = obj.particle_systems.active
setting = ps.settings
# Grids distribution not supported
if setting.distribution == 'GRID':
self.report({'INFO'},
"Grid distribution mode for particles not supported")
return{'CANCELLED'}
Btrace = bpy.context.window_manager.curve_tracer
# Get frame start
particle_f_start = Btrace.particle_f_start
# Get frame end
particle_f_end = Btrace.particle_f_end
curve_handle = Btrace.curve_handle
# hackish because of naming conflict in api
if curve_handle == 'AUTOMATIC':
curve_handle = 'AUTO'
if curve_handle == 'FREE_ALIGN':
curve_handle = 'FREE'
# define what kind of object to create
tracer = bpy.data.curves.new('Splines', 'CURVE')
# Create new object with settings listed above
curve = bpy.data.objects.new('Tracer', tracer)
# Link newly created object to the scene
# bpy.context.view_layer.objects.link(curve)
bpy.context.scene.collection.objects.link(curve)
# add a new Bezier point in the new curve
spline = tracer.splines.new('BEZIER')
spline.bezier_points.add(setting.count - 1)
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
tracer.dimensions = '3D'
tracer.resolution_u = Btrace.curve_u
tracer.bevel_resolution = Btrace.curve_resolution
tracer.fill_mode = 'FULL'
tracer.bevel_depth = Btrace.curve_depth
if Btrace.particle_auto:
f_start = int(setting.frame_start)
f_end = int(setting.frame_end + setting.lifetime)
else:
if particle_f_end <= particle_f_start:
particle_f_end = particle_f_start + 1
f_start = particle_f_start
f_end = particle_f_end
for bFrames in range(f_start, f_end):
bpy.context.scene.frame_set(bFrames)
if not (bFrames - f_start) % Btrace.particle_step:
for bFrames in range(setting.count):
if ps.particles[bFrames].alive_state != 'UNBORN':
e = bFrames
bp = spline.bezier_points[bFrames]
pt = ps.particles[e]
bp.co = pt.location
bp.handle_right_type = curve_handle
bp.handle_left_type = curve_handle
bp.keyframe_insert('co')
bp.keyframe_insert('handle_left')
bp.keyframe_insert('handle_right')
# Select new curve
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = curve
# Materials
trace_mats = addtracemat(curve.data)
if not trace_mats:
self.report({'WARNING'}, "Some Materials could not be added")
if Btrace.animate:
bpy.ops.curve.btgrow()
except Exception as e:
error_handlers(self, "particles.connect", e,
"Connect Particles could not be completed")
return {'CANCELLED'}
# Writing Tool
# Writes a curve by animating its point's radii
class OBJECT_OT_writing(Operator):
bl_idname = "curve.btwriting"
bl_label = "Write"
bl_description = ("Use Grease Pencil to write and convert to curves\n"
"Needs an existing Grease Pencil layer attached to the Scene")
@classmethod
def poll(cls, context):
gp = context.scene.grease_pencil
return gp and gp.layers
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
try:
# first check if the Grease Pencil is attached to the Scene
tool_settings = context.scene.tool_settings
source_data = tool_settings.grease_pencil_source
if source_data in {"OBJECT"}:
self.report({'WARNING'},
"Operation Cancelled. "
"The Grease Pencil data-block is attached to an Object")
return {"CANCELLED"}
Btrace = context.window_manager.curve_tracer
# this is hacky - store objects in the scene for comparison later
store_objects = [ob for ob in context.scene.objects]
gactive = context.active_object
# checking if there are any strokes the easy way
if not bpy.ops.gpencil.convert.poll():
self.report({'WARNING'},
"Operation Cancelled. "
"Are there any Grease Pencil Strokes ?")
return {'CANCELLED'}
bpy.ops.gpencil.convert(type='CURVE')
# get curve after convert (compare the scenes to get the difference)
scene_obj = context.scene.objects
check_materials = True
for obj in scene_obj:
if obj not in store_objects and obj.type == "CURVE":
gactiveCurve = obj
break
# render ready curve
gactiveCurve.data.resolution_u = Btrace.curve_u
# Set bevel resolution from Panel options
gactiveCurve.data.bevel_resolution = Btrace.curve_resolution
gactiveCurve.data.fill_mode = 'FULL'
# Set bevel depth from Panel options
gactiveCurve.data.bevel_depth = Btrace.curve_depth
writeObj = context.selected_objects
if Btrace.animate:
for i in writeObj:
context.view_layer.objects.active = i
bpy.ops.curve.btgrow()
# Materials
trace_mats = addtracemat(bpy.context.object.data)
if not trace_mats and check_materials is True:
check_materials = False
else:
for i in writeObj:
context.view_layer.objects.active = i
# Materials
trace_mats = addtracemat(bpy.context.object.data)
if not trace_mats and check_materials is True:
check_materials = False
# Delete grease pencil strokes
context.view_layer.objects.active = gactive
context.view_layer.objects.active = gactiveCurve
# Smooth object
bpy.ops.object.shade_smooth()
# Return to first frame
bpy.context.scene.frame_set(Btrace.anim_f_start)
if check_materials is False:
self.report({'WARNING'}, "Some Materials could not be added")
return{'FINISHED'}
except Exception as e:
error_handlers(self, "curve.btwriting", e,
"Grease Pencil conversion could not be completed")
return {'CANCELLED'}
# Create Curve
# Convert mesh to curve using either Continuous, All Edges, or Sharp Edges
# Option to create noise
class OBJECT_OT_convertcurve(Operator):
bl_idname = "object.btconvertcurve"
bl_label = "Btrace: Create Curve"
bl_description = ("Convert Mesh to Curve using either Continuous, "
"All Edges or Sharp Edges\n"
"Active Object has to be of a Mesh or Font type")
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.type in {"MESH", "FONT"})
def execute(self, context):
try:
Btrace = context.window_manager.curve_tracer
obj = context.object
# Convert Font
if obj.type == 'FONT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.convert(target='CURVE') # Convert edges to curve
bpy.context.object.data.dimensions = '3D'
# make a continuous edge through all vertices
if obj.type == 'MESH':
# Add noise to mesh
if Btrace.distort_curve:
for v in obj.data.vertices:
for u in range(3):
v.co[u] += Btrace.distort_noise * (rand_uniform(-1, 1))
if Btrace.convert_edgetype == 'CONTI':
# Start Continuous edge
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete(type='EDGE_FACE')
bpy.ops.mesh.select_all(action='DESELECT')
verts = bpy.context.object.data.vertices
li = []
p1 = rand_randint(0, len(verts) - 1)
for v in verts:
li.append(v.index)
li.remove(p1)
for z in range(len(li)):
x = []
for px in li:
d = verts[p1].co - verts[px].co # find distance from first vert
x.append(d.length)
p2 = li[x.index(min(x))] # find the shortest distance list index
verts[p1].select = verts[p2].select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.tool_settings.mesh_select_mode = [True, False, False]
bpy.ops.mesh.edge_face_add()
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
li.remove(p2) # remove item from list.
p1 = p2
# Convert edges to curve
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.convert(target='CURVE')
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
if Btrace.convert_edgetype == 'EDGEALL':
# Start All edges
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete(type='ONLY_FACE')
bpy.ops.object.mode_set()
bpy.ops.object.convert(target='CURVE')
for sp in obj.data.splines:
sp.type = Btrace.curve_spline
obj = context.object
# Set spline type to custom property in panel
bpy.ops.object.editmode_toggle()
bpy.ops.curve.spline_type_set(type=Btrace.curve_spline)
# Set handle type to custom property in panel
bpy.ops.curve.handle_type_set(type=Btrace.curve_handle)
bpy.ops.object.editmode_toggle()
obj.data.fill_mode = 'FULL'
# Set resolution to custom property in panel
obj.data.bevel_resolution = Btrace.curve_resolution
obj.data.resolution_u = Btrace.curve_u
# Set depth to custom property in panel
obj.data.bevel_depth = Btrace.curve_depth
# Smooth object
bpy.ops.object.shade_smooth()
# Modulate curve radius and add distortion
if Btrace.distort_curve:
scale = Btrace.distort_modscale
if scale == 0:
return {'FINISHED'}
for u in obj.data.splines:
for v in u.bezier_points:
v.radius = scale * round(rand_random(), 3)
return {'FINISHED'}
except Exception as e:
error_handlers(self, "object.btconvertcurve", e,
"Conversion could not be completed")
return {'CANCELLED'}
# Mesh Follow, trace vertex or faces
# Create curve at center of selection item, extruded along animation
# Needs to be an animated mesh!!!
class OBJECT_OT_meshfollow(Operator):
bl_idname = "object.btmeshfollow"
bl_label = "Btrace: Vertex Trace"
bl_description = "Trace Vertex or Face on an animated mesh"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object and context.object.type in {'MESH'})
def execute(self, context):
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
try:
Btrace = context.window_manager.curve_tracer
stepsize = Btrace.particle_step
obj = context.object
scn = context.scene
drawmethod = Btrace.fol_mesh_type # Draw from Edges, Verts, or Faces
if drawmethod == 'VERTS':
meshobjs = obj.data.vertices
if drawmethod == 'FACES':
meshobjs = obj.data.polygons # untested
if drawmethod == 'EDGES':
meshobjs = obj.data.edges # untested
# Frame properties
start_frame, end_frame = Btrace.fol_start_frame, Btrace.fol_end_frame
if start_frame > end_frame: # Make sure the math works
start_frame = end_frame - 5 # if start past end, goto (end - 5)
frames = int((end_frame - start_frame) / stepsize)
def getsel_option(): # Get selection objects
sel = []
# options are 'random', 'custom', 'all'
seloption, fol_mesh_type = Btrace.fol_sel_option, Btrace.fol_mesh_type
if fol_mesh_type == 'OBJECT':
pass
else:
if seloption == 'CUSTOM':
for i in meshobjs:
sel.append(i.index)
if seloption == 'RANDOM':
for i in list(meshobjs):
sel.append(i.index)
finalsel = int(len(sel) * Btrace.fol_perc_verts)
remove = len(sel) - finalsel
for i in range(remove):
sel.pop(rand_randint(0, len(sel) - 1))
if seloption == 'ALL':
for i in list(meshobjs):
return sel
def get_coord(objindex):
obj_co = [] # list of vector coordinates to use
frame_x = start_frame
for i in range(frames): # create frame numbers list
scn.frame_set(frame_x)
if drawmethod != 'OBJECT':
followed_item = meshobjs[objindex]
if drawmethod == 'VERTS':
# find Vert vector
g_co = obj.matrix_local @ followed_item.co
if drawmethod == 'FACES':
# find Face vector
g_co = obj.matrix_local @ followed_item.normal
if drawmethod == 'EDGES':
v1 = followed_item.vertices[0]
v2 = followed_item.vertices[1]
co1 = bpy.context.object.data.vertices[v1]
co2 = bpy.context.object.data.vertices[v2]
localcenter = co1.co.lerp(co2.co, 0.5)
if drawmethod == 'OBJECT':
g_co = objindex.location.copy()
obj_co.append(g_co)
frame_x = frame_x + stepsize
scn.frame_set(start_frame)
return obj_co
def make_curve(co_list):
Btrace = bpy.context.window_manager.curve_tracer
tracer = bpy.data.curves.new('tracer', 'CURVE')
tracer.dimensions = '3D'
spline = tracer.splines.new('BEZIER')
spline.bezier_points.add(len(co_list) - 1)
curve = bpy.data.objects.new('curve', tracer)
curvelist.append(curve)
# render ready curve
tracer.resolution_u = Btrace.curve_u
# Set bevel resolution from Panel options
tracer.bevel_resolution = Btrace.curve_resolution
tracer.fill_mode = 'FULL'
# Set bevel depth from Panel options
tracer.bevel_depth = Btrace.curve_depth
curve_handle = Btrace.curve_handle
# hackish AUTOMATIC doesn't work here
if curve_handle == 'AUTOMATIC':
curve_handle = 'AUTO'
# move bezier points to objects
for i in range(len(co_list)):
p = spline.bezier_points[i]
p.co = co_list[i]
p.handle_right_type = curve_handle
p.handle_left_type = curve_handle
return curve
# Run methods
# Check if Btrace group exists, if not create
bcollection = bpy.data.collections.keys()
if 'Btrace' not in bcollection:
mycol=bpy.data.collections.new(name="Btrace")
bpy.context.scene.collection.children.link(mycol)
Btrace = bpy.context.window_manager.curve_tracer
sel = getsel_option() # Get selection
curvelist = [] # list to use for grow curve
check_materials = True
if Btrace.fol_mesh_type == 'OBJECT':
vector_list = get_coord(obj)
curvelist.append(make_curve(vector_list))
make_curve(vector_list)
# curvelist.append(make_curve(vector_list)) # append happens in function
# Select new curves and add to group
bpy.ops.object.select_all(action='DESELECT')
for curveobject in curvelist:
if curveobject.type == 'CURVE':
bpy.context.view_layer.objects.active = curveobject
bpy.data.collections["Btrace"].objects.link(curveobject) #2.8 link obj to collection
bpy.context.scene.collection.objects.unlink(curveobject) # unlink from scene collection
# bpy.ops.object.group_link(group="Btrace")
# Materials
trace_mats = addtracemat(curveobject.data)
if not trace_mats and check_materials is True:
check_materials = False
if Btrace.animate: # Add grow curve
for curveobject in curvelist:
bpy.ops.curve.btgrow()
for curveobject in curvelist:
obj.select_set(False) # Deselect original object
if check_materials is False:
self.report({'WARNING'}, "Some Materials could not be added")
except Exception as e:
error_handlers(self, "object.btmeshfollow", e,
"Vertex Trace could not be completed")
# Add Tracer Material
def addtracemat(matobj):
try:
# Check if a material exists, skip if it does
matslots = bpy.context.object.data.materials.items()
if len(matslots) < 1: # Make sure there is only one material slot
Btrace = bpy.context.window_manager.curve_tracer
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
# Check if color blender is to be run
if not Btrace.mat_run_color_blender:
# Create Random color for each item
if Btrace.trace_mat_random:
# Use random color from chosen palette,
# assign color lists for each palette
brightColors = [
Btrace.brightColor1, Btrace.brightColor2,
Btrace.brightColor3, Btrace.brightColor4
]
bwColors = [
Btrace.bwColor1, Btrace.bwColor2
]
customColors = [
Btrace.mmColor1, Btrace.mmColor2, Btrace.mmColor3,
Btrace.mmColor4, Btrace.mmColor5, Btrace.mmColor6,
Btrace.mmColor7, Btrace.mmColor8
]
earthColors = [
Btrace.earthColor1, Btrace.earthColor2,
Btrace.earthColor3, Btrace.earthColor4,
Btrace.earthColor5
]
greenblueColors = [
Btrace.greenblueColor1, Btrace.greenblueColor2,
Btrace.greenblueColor3
]
if Btrace.mmColors == 'BRIGHT':
mat_color = brightColors[
rand_randint(0, len(brightColors) - 1)
]
if Btrace.mmColors == 'BW':
mat_color = bwColors[
rand_randint(0, len(bwColors) - 1)
]
if Btrace.mmColors == 'CUSTOM':
mat_color = customColors[
rand_randint(0, len(customColors) - 1)
]
if Btrace.mmColors == 'EARTH':
mat_color = earthColors[
rand_randint(0, len(earthColors) - 1)
]
if Btrace.mmColors == 'GREENBLUE':
mat_color = greenblueColors[
rand_randint(0, len(greenblueColors) - 1)
]
if Btrace.mmColors == 'RANDOM':
mat_color = (rand_random(), rand_random(), rand_random())
# Choose Single color
mat_color = Btrace.trace_mat_color
TraceMat = bpy.data.materials.new('TraceMat')
BSDF = TraceMat.node_tree.nodes[1]
r, g, b = mat_color[0], mat_color[1], mat_color[2]
BSDF.inputs[0].default_value = [r, g, b, 1] # change node color
TraceMat.diffuse_color = [r, g, b, 1] # change viewport color
# Add material to object
matobj.materials.append(bpy.data.materials.get(TraceMat.name))
else:
# Run color blender operator
bpy.ops.object.colorblender()
except Exception as e:
error_handlers(False, "addtracemat", e, "Function error", True)
return False
# Add Color Blender Material
# This is the magical material changer!
class OBJECT_OT_materialChango(Operator):
bl_idname = "object.colorblender"
bl_label = "Color Blender"
bl_options = {'REGISTER', 'UNDO'}
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
def execute(self, context):
try:
# properties panel
Btrace = context.window_manager.curve_tracer
colorObjects = context.selected_objects
# Set color lists
brightColors = [
Btrace.brightColor1, Btrace.brightColor2,
Btrace.brightColor3, Btrace.brightColor4
]
bwColors = [Btrace.bwColor1, Btrace.bwColor2]
customColors = [
Btrace.mmColor1, Btrace.mmColor2, Btrace.mmColor3,
Btrace.mmColor4, Btrace.mmColor5, Btrace.mmColor6,
Btrace.mmColor7, Btrace.mmColor8
]
earthColors = [
Btrace.earthColor1, Btrace.earthColor2, Btrace.earthColor3,
Btrace.earthColor4, Btrace.earthColor5
]
greenblueColors = [
Btrace.greenblueColor1, Btrace.greenblueColor2,
Btrace.greenblueColor3
]
engine = context.scene.render.engine
# Go through each selected object and run the operator
for i in colorObjects:
theObj = i
# Check to see if object has materials
checkMaterials = len(theObj.data.materials)
if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
materialName = "colorblendMaterial"
madMat = bpy.data.materials.new(materialName)
madMat.use_nodes = True
if checkMaterials == 0:
theObj.data.materials.append(madMat)
else:
theObj.material_slots[0].material = madMat
else: # This is internal
if checkMaterials == 0:
# Add a material
materialName = "colorblendMaterial"
madMat = bpy.data.materials.new(materialName)
theObj.data.materials.append(madMat)
else:
pass # pass since we have what we need
# assign the first material of the object to "mat"
madMat = theObj.data.materials[0]
# Numbers of frames to skip between keyframes
skip = Btrace.mmSkip
# Random material function
def colorblenderRandom():
randomRGB = (rand_random(), rand_random(), rand_random(), 1)
if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
Principled_BSDF = madMat.node_tree.nodes[1]
mat_color = randomRGB
r, g, b = mat_color[0], mat_color[1], mat_color[2]
Principled_BSDF.inputs[0].default_value = [r, g, b, 1]
madMat.diffuse_color = mat_color[0], mat_color[1], mat_color[2], 1
else:
madMat.diffuse_color = randomRGB
def colorblenderCustom():
if engine == 'CYCLES' or engine == 'BLENDER_EEVEE':
Principled_BSDF = madMat.node_tree.nodes[1]
mat_color = rand_choice(customColors)
r, g, b = mat_color[0], mat_color[1], mat_color[2]
Principled_BSDF.inputs[0].default_value = [r, g, b, 1]
madMat.diffuse_color = mat_color[0], mat_color[1], mat_color[2], 1
else:
madMat.diffuse_color = rand_choice(customColors)