# --------- # Tested in Blender 2.75a and 2.76 # Makes a 'replicator grid' -- multiple copies of a given object, parented in a tranformation-control chain. # Provides a single empty that controls the relative size, displacement and rotation of each successive object in relation to # its parent. # # Usage: # Change the number of rows and columns below. # Select target object to be replicated. # Run the script. # If you did this with an object named 'cube', for example, # You will end up with a 'cube_replicator' empty, below which all of the duplicated objects # have been parented. # And you will have a new cube_replicator_control empty, which can be moved, scaled and rotated # to control the overall replicator grid. # Bret Battey / BatHatMedia.com September 2015 # --------- #import bpy from bpy.props import * # specify number of rows and columns here! rows = 5 columns = 5 # The following function is adapted from Nick Keeline "Cloud Generator" # addNewObject in object_cloud_gen.py def duplicateObject(scene, name, copyobj): # Create new mesh mesh = bpy.data.meshes.new(name) # Create a new object. ob_new = bpy.data.objects.new(name, mesh) tempme = copyobj.data ob_new.data = tempme.copy() ob_new.scale = copyobj.scale ob_new.location = copyobj.location # Link new object to the given scene and select it. scene.objects.link(ob_new) ob_new.select = True return ob_new # Build the parenting chain of replications tied to empties and control # constraints. If we built a parented chain of objects, we wouldn't be able to # transform each object independently. So instead we build a parented chain of # empties, then parent each duplicated object to one of the empties. def gridOfReps(rows, cols, source_obj, ctrl, scene): for row in range(rows): for col in range(cols): #Create a locator empty bpy.ops.object.add(type="EMPTY", location=(0,0,0)) rep_loc = bpy.context.object # The first empty becomes the root of the whole beast, and the # source object gets parented to it if((row == 0) and (col == 0)): rep_loc.name = source_obj.name+"_replicator" parent_obj = rep_loc prev_row_locator = rep_loc source_parent = source_obj.parent # If the source object has a parent, locate and parent # the new replicator base empty to that. if((source_parent) and (source_parent.type == 'EMPTY')): rep_loc.location = source_parent.location rep_loc.parent = source_parent # Otherwise just position the base empty to the source obj, # zero the source obj location, and parent to the base else: rep_loc.location = source_obj.location source_obj.location = (0,0,0) source_obj.parent = rep_loc else: rep_loc.name = "rep_loc_r"+str(row)+"c_"+str(col) # name can't #be specified via the .add method rep_loc.parent = parent_obj # parent to the previous locator # First locator in each row other than 0 gets special treatment if((row > 0) and (col == 0)): # First location constraint locks to the prev. row locator constraint = rep_loc.constraints.new("COPY_LOCATION") constraint.target = prev_row_locator constraint.use_x = True constraint.use_y = True constraint.use_z = True constraint.owner_space = "WORLD" constraint.target_space = "WORLD" constraint.influence = 1.0 # Second location constraint implements the z offset # from the controller constraint = rep_loc.constraints.new("COPY_LOCATION") constraint.target = ctrl constraint.use_x = False constraint.use_y = False constraint.use_z = True constraint.owner_space = "LOCAL" constraint.target_space = "WORLD" constraint.influence = 1.0 constraint.use_offset = True # use the controller's # location as an offset rather than an absolute location # And ready this locator to be basis of next row position prev_row_locator = rep_loc else: # For other items in a row, offset location x on basis of # controller constraint = rep_loc.constraints.new("COPY_LOCATION") constraint.target = ctrl constraint.use_x = True # for offsetting a row, only x is # drawn from the controller constraint.use_y = False constraint.use_z = False constraint.owner_space = "LOCAL" constraint.target_space = "WORLD" constraint.influence = 1.0 constraint.use_offset = True # use the controller's #location as an offset rather than an absolute location # For all locators except the root, apply these influences from #the controller: # Copy_Rotation Constraint constraint = rep_loc.constraints.new("COPY_ROTATION") constraint.target = ctrl constraint.owner_space = "LOCAL" constraint.target_space = "WORLD" constraint.influence = 1.0 # Copy_Scale Constraint. This will also cascade down the chain # due to the combined effect with the parenting constraint = rep_loc.constraints.new("COPY_SCALE") constraint.target = ctrl constraint.owner_space = "LOCAL" constraint.target_space = "WORLD" constraint.influence = 1.0 # In all cases (except root), duplicate source object and parent # to the replicator empty. # This strategy allows later transformations of the visible #object without influencing the children new_obj = duplicateObject(scene, "rep", source_obj) new_obj.parent = rep_loc # Advance parent assignment parent_obj = rep_loc return parent_obj # get the source object source_obj = bpy.context.object source_loc = source_obj.location source_scene = bpy.context.scene # create and name the controller empty bpy.ops.object.add(type="EMPTY", location=(0,0,0)) ctrl = bpy.context.object ctrl.name = source_obj.name+"_replicator_control" # name can't be specified #via the .add method #replicate! last_locator = gridOfReps(rows, columns, source_obj, ctrl, source_scene)
Bret Battey blogging sundry ideas, favorable events, works in progress, and miscellaneous solutions in digital music and video-music research and creation. I see this as a subsidiary of my web site, BatHatMedia.com.
Tuesday, February 16, 2016
"Replicator Grid" Blender script
This Blender script creates a 2D grid of replicated objects with a descending line of parenting control, running left to right, then down to the next row. The following video demonstrates:
Blender Script for creating animated motion-trail ribbon
Given an object for which a motion-path has been generated, will create a BeziƩr-spline ribbon that follows the motion of the object for a given tail length (designated in frames).
# --------- # Tested in Blender 2.75a and 2.76 # Makes a Bezier-spline motion trail for an object. # Set the length of the tail in frames via the tail_length variable below. # To run, select object, generate a motion path for the object (Editor>Motion Paths) across the total desired frame range, and Run Script. # Warning: this can take a long time to run with long motion paths and long tail sizes # Once the spline has been generated, extrude and/or add bevel and taper objects to the Geometry settings of the spline. # Bret Battey / BatHatMedia.com September 2015 # --------- import bpy from bpy.props import * from mathutils import * from math import * import time # ----- FUNCTION DEFINITIONS ----- # Time utils thanks to http://blenderscripting.blogspot.co.uk/search/label/time def get_last_time(): if len(time_list) < 2: return "ERROR: must have two time entries to calculate the difference" return time_list[-1] - time_list[-2] def mark_time(): time_list.append(time.time()) time_list = [] ##------------------------------------------------------------ #### Curve creation functions # sets bezierhandles to auto def setBezierHandles(obj, mode = 'AUTOMATIC'): scene = bpy.context.scene if obj.type != 'CURVE': return scene.objects.active = obj bpy.ops.object.mode_set(mode='EDIT', toggle=True) bpy.ops.curve.select_all(action='SELECT') bpy.ops.curve.handle_type_set(type=mode) bpy.ops.object.mode_set(mode='OBJECT', toggle=True) # create new CurveObject def createCurve(verts, name): # create curve scene = bpy.context.scene newCurve = bpy.data.curves.new(name, type = 'CURVE') # curvedatablock newSpline = newCurve.splines.new(type = 'BEZIER') # spline # The new spline already has one point. Add the remaining needed. newSpline.bezier_points.add(verts-1) # set curveOptions newCurve.dimensions = '3D' # create object with newCurve new_obj = bpy.data.objects.new(name, newCurve) # object scene.objects.link(new_obj) # place in active scene new_obj.select = True # set as selected scene.objects.active = new_obj # set as active # set bezierhandles setBezierHandles(new_obj) return new_obj #based on animation_rotobezier.py def keyframeBezier(Obj, frame): Data = Obj.data for Spline in Data.splines: for CV in Spline.bezier_points: CV.keyframe_insert(data_path='co', frame = frame) CV.keyframe_insert(data_path='handle_left', frame = frame) CV.keyframe_insert(data_path='handle_right', frame = frame) # ------------- SCRIPT START tail_length = 60 # length of the spline in frames object = bpy.context.object path = object.motion_path frame_start = path.frame_start frame_end = path.frame_end path_length = path.length # --- create the Bezier curve trail = createCurve(tail_length,object.name+" motion_trail") mark_time() # start measuring elapsed time # --- key frame the curve at every frame in the motion path for i in range(tail_length-1,path_length): # motion path index print("Point "+str(i)) for j in range(tail_length): # curve point index trail.data.splines[0].bezier_points[j].co = path.points[(i-tail_length)+j].co # set the coordinates of the motion tail to the corresponding point on the motion path # keyframe it keyframeBezier(trail, frame_start+i) mark_time() print('The motion trail keyframing took {: 5g} seconds'.format(get_last_time()))
Blender Script for object-motion measurements
In Blender, given an object that has a motion path, will provide animated empties which indicate the object's velocity, acceleration, magnitude, and accumulated distance moved.
# --------- # Tested on Blender 2.75a and 2.76 # For a given object, makes an empty, which moves to indicate the velocity (displacement between frames) for each axis of the original object # and another empty providing magnitude of the movement # and another empty providing accumulative displacement for each axis, # and yet another for acceleration. # To run, select object, generate a motion path for the object (Editor>Motion Paths) across the total desired frame range, and Run Script. # Bret Battey / BatHatMedia.com Dec 2015 # --------- import bpy from bpy.props import * from mathutils import * from math import * import time # ----- FUNCTION DEFINITIONS ----- # Time utils thanks to http://blenderscripting.blogspot.co.uk/search/label/time def get_last_time(): if len(time_list) < 2: return "ERROR: must have two time entries to calculate the difference" return time_list[-1] - time_list[-2] def mark_time(): time_list.append(time.time()) time_list = [] # ------------- SCRIPT START object = bpy.context.object path = object.motion_path frame_start = path.frame_start frame_end = path.frame_end path_length = path.length # velocity empty bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, view_align=False, location=(0,0,0), layers=object.layers) velempty = bpy.context.object velempty.name = object.name + '_velocity' # vector magnitude bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, view_align=False, location=(0,0,0), layers=object.layers) magempty = bpy.context.object magempty.name = object.name + '_magnitude' # accumulative distance traveled bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, view_align=False, location=(0,0,0), layers=object.layers) accumempty = bpy.context.object accumempty.name = object.name + '_accum_distance' # acceleration bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, view_align=False, location=(0,0,0), layers=object.layers) accelempty = bpy.context.object accelempty.name = object.name + '_acceleration' mark_time() # start measuring elapsed time for i in range(0,path_length-2): # motion path index nextPointCo = path.points[i+1].co # next time point thisPointCo = path.points[i].co # this time point dif = nextPointCo-thisPointCo # x,y,z distances mag = sqrt(dif[0]*dif[0]+dif[1]*dif[1]+dif[2]*dif[2]) # vector magnitude via pythagorean theorem velempty.location = dif # set vel empty's location to the velocity vector magempty.location[2] = mag # set z position of mag empty's location to the magnitude frame = frame_start+i # get absolute frame number velempty.keyframe_insert(data_path='location',frame=frame,index=-1) # set keyframe for velocity. index -1 = set all three axes magempty.keyframe_insert(data_path='location',frame=frame,index=2) # set keyframe for velocity. index -1 = set all three axes if i == 0: accumempty.keyframe_insert(data_path='location',frame=frame,index=-1) # set keyframe. index -1 = set all three axes accumdif = accumempty.location # start accumulated distance with a convenient 0,0,0 vector else: accumdif[0] += abs(dif[0]) # add absolute value of velocity to the accumulative distance accumdif[1] += abs(dif[1]) accumdif[2] += abs(dif[2]) accumempty.location = accumdif # move empty to this point accumempty.keyframe_insert(data_path='location',frame=frame,index=-1) # set keyframe. index -1 = set all three axes accelempty.location = dif-lastdif # acceleration = dif between this frame's velocity and the previous frame's accelempty.keyframe_insert(data_path='location',frame=frame-1,index=-1) # set keyframe ONE FRAME BACK. index -1 = set all three axes lastdif = dif mark_time() print('The keyframing took {: 5g} seconds'.format(get_last_time()))
Subscribe to:
Posts (Atom)