def execute(self,context): settings = get_settings() dbg = settings.debug #TODO: Scene Preservation recording teeth = odcutils.tooth_selection(context) sce = bpy.context.scene layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] for tooth in teeth: #see if there is a corresponding implant if tooth.name in sce.odc_implants: contour = bpy.data.objects.get(tooth.contour) Z = Vector((0,0,-1)) if contour: if tooth.axis: Axis = bpy.data.objects.get(tooth.axis) if Axis: neg_z = Axis.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z) else: neg_z = contour.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z) else: neg_z = contour.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z) mx = contour.matrix_world x = mx[0][3] y = mx[1][3] z = mx[2][3] #CEJ Location new_loc = odcutils.box_feature_locations(contour, Vector((0,0,-1))) Imp = implant_utils.place_implant(context, sce.odc_implants[tooth.name], new_loc, rot_diff, self.imp, hardware = self.hardware) #reposition platform below CEJ world_mx = Imp.matrix_world delta = Imp.dimensions[2] * world_mx.to_3x3() * Vector((0,0,1)) + self.depth * world_mx.to_3x3() * Vector((0,0,1)) world_mx[0][3] += delta[0] world_mx[1][3] += delta[1] world_mx[2][3] += delta[2] #odcutils.reorient_object(Imp, rot_diff) odcutils.layer_management(sce.odc_implants, debug = False) for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[11] = True return {'FINISHED'}
def execute(self, context): settings = get_settings() dbg = settings.debug #TODO: Scene Preservation recording teeth = odcutils.tooth_selection(context) sce = bpy.context.scene layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] for tooth in teeth: #see if there is a corresponding implant if tooth.name in sce.odc_implants: contour = bpy.data.objects.get(tooth.contour) Z = Vector((0, 0, -1)) if contour: if tooth.axis: Axis = bpy.data.objects.get(tooth.axis) if Axis: neg_z = Axis.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs( Vector((0, 0, 1)), neg_z) else: neg_z = contour.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs( Vector((0, 0, 1)), neg_z) else: neg_z = contour.matrix_world.to_quaternion() * Z rot_diff = odcutils.rot_between_vecs( Vector((0, 0, 1)), neg_z) mx = contour.matrix_world x = mx[0][3] y = mx[1][3] z = mx[2][3] #CEJ Location new_loc = odcutils.box_feature_locations( contour, Vector((0, 0, -1))) Imp = implant_utils.place_implant( context, sce.odc_implants[tooth.name], new_loc, rot_diff, self.imp, hardware=self.hardware) #reposition platform below CEJ world_mx = Imp.matrix_world delta = Imp.dimensions[2] * world_mx.to_3x3() * Vector( (0, 0, 1)) + self.depth * world_mx.to_3x3() * Vector( (0, 0, 1)) world_mx[0][3] += delta[0] world_mx[1][3] += delta[1] world_mx[2][3] += delta[2] #odcutils.reorient_object(Imp, rot_diff) odcutils.layer_management(sce.odc_implants, debug=False) for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[11] = True return {'FINISHED'}
def teeth_to_curve(context, arch, sextant, tooth_library, teeth = [], shift = 'BUCCAL', limit = False, link = False, reverse = False, mirror = False, debug = False, reorient = True): ''' puts teeth along a curve for full arch planning args: curve - blender Curve object sextant - the quadrant or sextant that the curve corresponds to. enum in 'MAX', 'MAND', 'UR' 'LR' 'LR' 'LL' 'UA' 'LA' ' teeth - list of odc_teeth, to link to or from. eg, if tooth already a restoration it will use that object, if not, it will link a new blender object to that tooth as the restoration or contour. shift = whether to use buccal cusps, center of mass or, center of fossa to align onto cirve. enum in 'BUCCAL', 'COM', 'FOSSA' limit - only link teeth for each tooth in teeth link - Bool, whether or not to link to/from the teeth list ''' if debug: start = time.time() orig_arch_name = arch.name bpy.ops.object.select_all(action='DESELECT') context.scene.objects.active = arch arch.hide = False arch.select = True if mirror: #This should help with the mirroring? arch.data.resolution_u = 5 #if it doesn't have a mirror, we need to mirror it if "Mirror" not in arch.modifiers: bpy.ops.object.modifier_add(type='MIRROR') #non mirrored curve needed for appropriate constraining.. #convert to mesh applies mirror, reconvert to curve gives us a full length curve arch.modifiers["Mirror"].merge_threshold = 5 bpy.ops.object.convert(target='MESH',keep_original = True) bpy.ops.object.convert(target='CURVE', keep_original = False) #this will be the new full arch arch = context.object arch.name = orig_arch_name + "_Mirrored" #we may want to switch the direction of the curve :-) #we may also want to handle this outside of this function if reverse: bpy.ops.object.mode_set(mode='EDIT') bpy.ops.curve.switch_direction() bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.convert(target='MESH', keep_original = True) arch_mesh = context.object #now the mesh conversion arch_len = 0 mx = arch_mesh.matrix_world #do some calcs to the curve #TODO: split this method off. It may already #be in odcutils. occ_dir = Vector((0,0,0)) #this will end be a normalized, global direction for i in range(0,len(arch_mesh.data.vertices)-1): v0 = arch_mesh.data.vertices[i] v1 = arch_mesh.data.vertices[i+1] V0 = mx*v1.co - mx*v0.co arch_len += V0.length if i < len(arch_mesh.data.vertices)-2: v2 = arch_mesh.data.vertices[i+2] V1 = mx*v2.co - mx*v1.co occ_dir += V0.cross(V1) if debug: print("arch is %f mm long" % arch_len) #pull values from the tooth size/data #if we are mirroring, we need to do some logic if mirror: if sextant not in ["UR","UL","LR","LL","MAX","MAND"]: print('Incorrect sextant for mirroring') return {'CANCELLED'} else: if sextant.startswith("U"): sextant = "MAX" elif sextant.startswith("L"): sextant = "MAND" #else..leave quadrant alone curve_teeth = quadrant_dict[sextant] occ_dir *= occ_direct_dict[sextant] * 1/(len(arch_mesh.data.vertices)-2) occ_dir.normalize() #this deletes the arch mesh...not the arch curve bpy.ops.object.delete() if reorient: arch_z = mx.to_quaternion() * Vector((0,0,1)) arch_z.normalize() if math.pow(arch_z.dot(occ_dir),2) < .9: orient = odcutils.rot_between_vecs(Vector((0,0,1)), occ_dir) #align the local Z of bezier with occlusal direction (which is global). odcutils.reorient_object(arch, orient) if debug: print("working on these teeth %s" % ":".join(curve_teeth)) #import/link teeth from the library restorations = [] if link and len(context.scene.odc_teeth): for tooth in context.scene.odc_teeth: if tooth.name[0:2] in curve_teeth: #TODO: restoration etc? #we will have to check later if we need to use the restoration #from this tooth restorations.append(tooth.name) if debug: print("These restorations are already in the proposed quadrant %s" % ", ".join(restorations)) #figure out which objects we are going to distribute. lib_teeth_names = odcutils.obj_list_from_lib(tooth_library) #TODO: check if tooth_library is valid? tooth_objects=[[None]]*len(curve_teeth) #we want this list to be mapped to curve_teeth with it's index...dictionary if we have to delete_later = [] for i, planned_tooth in enumerate(curve_teeth): #this will be a one item list tooth_in_scene = [tooth for tooth in context.scene.odc_teeth if tooth.name.startswith(planned_tooth)] if link and len(tooth_in_scene): #check if the restoration is already there...if so, use it if tooth_in_scene[0].contour: tooth_objects[i] = bpy.data.objects[tooth_in_scene[0].contour] #if it's not there, add it in, and associate it with ODCTooth Object else: for tooth in lib_teeth_names: if tooth.startswith(planned_tooth): #necessary that the planned teeth have logical names new_name = tooth + "_ArchPlanned" if new_name in bpy.data.objects: ob = bpy.data.objects[new_name] me = ob.data ob.user_clear() bpy.data.objects.remove(ob) bpy.data.meshes.remove(me) context.scene.update() odcutils.obj_from_lib(tooth_library, tooth) ob = bpy.data.objects[tooth] context.scene.objects.link(ob) ob.name = new_name tooth_objects[i] = ob tooth_in_scene[0].contour = ob.name break #in case there are multiple copies? else: #the tooth is not existing restoration, and we want to put it in anyway for tooth in lib_teeth_names: if tooth.startswith(planned_tooth): new_name = tooth + "_ArchPlanned" if new_name in bpy.data.objects: ob = bpy.data.objects[new_name] me = ob.data ob.user_clear() bpy.data.objects.remove(ob) bpy.data.meshes.remove(me) context.scene.update() odcutils.obj_from_lib(tooth_library, tooth) ob = bpy.data.objects[tooth] ob.name += "_ArchPlanned" if limit: context.scene.objects.link(ob) delete_later.append(ob) else: context.scene.objects.link(ob) tooth_objects[i]= ob break if debug: print(tooth_objects) #secretly, we imported the whole quadrant..we will delete them later teeth_len = 0 lengths = [[0]] * len(curve_teeth) #list of tooth mesial/distal lengths locs = [[0]] * len(curve_teeth) #normalized list of locations for i, ob in enumerate(tooth_objects): lengths[i] = ob.dimensions[0] teeth_len += ob.dimensions[0] locs[i] = teeth_len - ob.dimensions[0]/2 scale = arch_len/teeth_len crowding = teeth_len - arch_len if debug > 1: print(lengths) print(locs) print(scale) print("there is %d mm of crowding" % round(crowding,2)) print("there is a %d pct archlength discrepancy" % round(100-scale*100, 2)) #scale them to the right size for i, ob in enumerate(tooth_objects): if shift == 'FOSSA': delta = .05 else: delta = 0 #resize it ob.scale[0] *= scale + delta ob.scale[1] *= scale + delta ob.scale[2] *= scale + delta #find the location of interest we want? # bbox center, cusp tip? fossa/grove, incisal edge? #TODO: odcutils.tooth_features(tooth,feature) (world coords or local?) ob.location = Vector((0,0,0)) if ob.rotation_mode != 'QUATERNION': ob.rotation_mode = 'QUATERNION' ob.rotation_quaternion = Quaternion((1,0,0,0)) #center line...we want palatinal face median point z,y with midpointx and center line min local z #buccal line...we want incisal edge median local y, maxlocal z, midpoing bbox x and buccal cusp max z? context.scene.objects.active = ob ob.select = True ob.hide = False ob.constraints.new('FOLLOW_PATH') path_constraint = ob.constraints["Follow Path"] path_constraint.target = arch path_constraint.use_curve_follow = True #find out if we cross the midline if sextant in ['MAX','MAND','UA','LA']: path_constraint.forward_axis = 'FORWARD_X' if int(curve_teeth[i]) > 20 and int(curve_teeth[i]) < 40: path_constraint.forward_axis = 'TRACK_NEGATIVE_X' else: path_constraint.forward_axis = 'FORWARD_X' path_constraint.offset = 100*(-1 + locs[i]/teeth_len) #after arranging them on the curve, make a 2nd pass to spin them or not #decrease in number means mesial. Except at midline.this will happen #we have constructed curve_teeth such that there will never be a non #integer change in adjacent list members. #eg, #quaternion rotation rules # Qtotal = Qa * Qb represtnts rotation b followed by rotation a #what we are doing is testing the occlusal direction of one tooth vs the arch occlusal direction context.scene.update() ob_dist = tooth_objects[1] ob_mes = tooth_objects[0] mesial = int(curve_teeth[1]) - int(curve_teeth[0]) == 1 #if true....distal numbers > mesial numbers vect = ob_mes.matrix_world * ob_mes.location - ob_dist.matrix_world * ob_dist.location spin = (vect.dot(ob_dist.matrix_world.to_quaternion() * Vector((1,0,0))) < 0) == mesial tooth_occ = ob_mes.matrix_world.to_quaternion() * Vector((0,0,1)) flip = tooth_occ.dot(occ_dir) > 0 if debug: print('We will flip the teeth: %s. We will spin the teeth: %s.' % (str(flip), str(spin))) for ob in tooth_objects: if flip: ob.rotation_quaternion = Quaternion((0,1,0,0)) if spin: ob.rotation_quaternion = Quaternion((0,0,0,1)) * ob.rotation_quaternion for i, ob in enumerate(tooth_objects): if shift == 'BUCCAL': groups = ["Incisal Edge", "Distobuccal Cusp","Mesiobuccal Cusp", "Buccal Cusp"] inds = [] for vgroup in groups: if vgroup in ob.vertex_groups: inds += odcutils.vert_group_inds_get(context, ob, vgroup) max_z = 0 max_ind = 0 for j in inds: z = ob.data.vertices[j].co[2] if z > max_z: max_ind = j max_z = z tip = ob.data.vertices[max_ind].co tooth_shift = Vector((0,tip[1]*ob.scale[1],tip[2]*ob.scale[2])) if sextant in ['MAX','MAND','UA','LA']: #no freakin idea why this is happening, but empirically, it's working tooth_shift[1]*= -1 ob.location += (-1 + 2*flip) * tooth_shift if shift == 'FOSSA': groups = ["Middle Fissure", "Palatinal Face"] inds = [] for vgroup in groups: if vgroup in ob.vertex_groups and vgroup == "Middle Fissure": inds += odcutils.vert_group_inds_get(context, ob, vgroup) min_z = ob.dimensions[2] min_ind = 0 for j in inds: z = ob.data.vertices[j].co[2] if z < min_z: min_ind = j min_z = z depth = ob.data.vertices[min_ind].co tooth_shift = Vector((0,depth[1]*ob.scale[1],depth[2]*ob.scale[2])) elif vgroup in ob.vertex_groups and vgroup == "Palatinal Face": inds += odcutils.vert_group_inds_get(context, ob, vgroup) mx = Matrix.Identity(4) com = odcutils.get_com(ob.data, inds, mx) tooth_shift = odcutils.scale_vec_mult(com, ob.matrix_world.to_scale()) if sextant in ['MAX','MAND','UA','LA']: #no freakin idea why this is happening, but empirically, it's working tooth_shift[1]*= -1 ob.location += (-1 + 2*flip) * tooth_shift if limit: bpy.ops.object.select_all(action='DESELECT') for ob in delete_later: ob.select = True context.scene.objects.active = ob bpy.ops.object.delete()
def break_contact_deform(context, ob1,ob2, debug = False): ''' separate two objects by deforming a lattice with a plane. Results in a smooth separation. args: ret: ''' if debug: print('ob1 name: %s' % ob1.name) print('ob2 name: %s' % ob2.name) quat_1 = ob1.matrix_world.to_quaternion() quat_2 = ob2.matrix_world.to_quaternion() lat1 = odcutils.bbox_to_lattice(context.scene, ob1) lat2 = odcutils.bbox_to_lattice(context.scene, ob2) print('we made lattices?') loc_1 = odcutils.get_bbox_center(ob1, world = True) loc_2 = odcutils.get_bbox_center(ob2, world = True) diff = loc_2 - loc_1 #the directions to keep things simple. x = Vector((1,0,0)) y = Vector((0,1,0)) z = Vector((0,0,1)) vecs = [x,y,z] #dot each of the x,y,z coords (transformed to workd dir) with the vector between #the two bounding box centers. dirs1 = [(quat_1 * x).dot(diff)**2, (quat_1 * y).dot(diff)**2, (quat_1 * z).dot(diff)**2] dirs2 = [(quat_2 * x).dot(diff)**2, (quat_2 * y).dot(diff)**2, (quat_2 * z).dot(diff)**2] #find the maximium dot product #this is the dirction which is most parallel dir1 = dirs1.index(max(dirs1)) dir2 = dirs2.index(max(dirs2)) #check i we need to negate eithe directions #don't get confused because we will negate again #when we put the shrinwrap mod on. This is determinging #whether +x or -x points at the othe robject neg1 = 1 + -2 * ((quat_1 * vecs[dir1]).dot(diff) < 0) neg2 = 1 + -2 * ((quat_2 * vecs[dir2]).dot(diff) > 0) vec1 = neg1 * vecs[dir1] vec2 = neg2 * vecs[dir2] if debug: print(ob1.name + ' is pointed toward ' + ob2.name + ' in the direction:') print(vec1) print(ob2.name + ' is pointed toward ' + ob1.name + ' in the direction:') print(vec2) pt1 = odcutils.box_feature_locations(ob1, vec1) pt2 = odcutils.box_feature_locations(ob2, vec2) if debug: print(pt1) print(pt2) midpoint = .5 * (pt1 + pt2) pln_verts = [Vector((1,1,0)),Vector((-1,1,0)),Vector((-1,-1,0)),Vector((1,-1,0))] pln_faces = [(0,1,2,3)] pln_mesh = bpy.data.meshes.new('separator') pln_mesh.from_pydata(pln_verts,[],pln_faces) new_plane_ob = bpy.data.objects.new('Separator', pln_mesh) new_plane_ob.rotation_mode = 'QUATERNION' new_plane_ob.rotation_quaternion = odcutils.rot_between_vecs(Vector((0,0,1)), diff) new_plane_ob.location = midpoint new_plane_ob.scale = .5 * (ob1.dimensions + ob2.dimensions) context.scene.objects.link(new_plane_ob) mod1 = lat1.modifiers.new('Contact', 'SHRINKWRAP') mod2 = lat2.modifiers.new('Contact', 'SHRINKWRAP') mod1.wrap_method = 'PROJECT' mod2.wrap_method = 'PROJECT' if neg1 < 0: mod1.use_negative_direction = False mod1.use_positive_direction = True else: mod1.use_negative_direction = True mod1.use_positive_direction = False if neg2 < 0: mod2.use_negative_direction = False mod2.use_positive_direction = True else: mod2.use_negative_direction = True mod2.use_positive_direction = False if dir1 == 0: mod1.use_project_x = True elif dir1 == 1: mod1.use_project_y = True else: mod1.use_project_z = True if dir2 == 0: mod2.use_project_x = True elif dir2 == 1: mod2.use_project_y = True else: mod2.use_project_z = True mod1.target = new_plane_ob mod2.target = new_plane_ob print('broken!')
def teeth_to_curve(context, arch, sextant, tooth_library, teeth=[], shift='BUCCAL', limit=False, link=False, reverse=False, mirror=False, debug=False, reorient=True): ''' puts teeth along a curve for full arch planning args: curve - blender Curve object sextant - the quadrant or sextant that the curve corresponds to. enum in 'MAX', 'MAND', 'UR' 'LR' 'LR' 'LL' 'UA' 'LA' ' teeth - list of odc_teeth, to link to or from. eg, if tooth already a restoration it will use that object, if not, it will link a new blender object to that tooth as the restoration or contour. shift = whether to use buccal cusps, center of mass or, center of fossa to align onto cirve. enum in 'BUCCAL', 'COM', 'FOSSA' limit - only link teeth for each tooth in teeth link - Bool, whether or not to link to/from the teeth list ''' if debug: start = time.time() orig_arch_name = arch.name bpy.ops.object.select_all(action='DESELECT') context.scene.objects.active = arch arch.hide = False arch.select = True if mirror: #This should help with the mirroring? arch.data.resolution_u = 5 #if it doesn't have a mirror, we need to mirror it if "Mirror" not in arch.modifiers: bpy.ops.object.modifier_add(type='MIRROR') #non mirrored curve needed for appropriate constraining.. #convert to mesh applies mirror, reconvert to curve gives us a full length curve arch.modifiers["Mirror"].merge_threshold = 5 bpy.ops.object.convert(target='MESH', keep_original=True) bpy.ops.object.convert( target='CURVE', keep_original=False) #this will be the new full arch arch = context.object arch.name = orig_arch_name + "_Mirrored" #we may want to switch the direction of the curve :-) #we may also want to handle this outside of this function if reverse: bpy.ops.object.mode_set(mode='EDIT') bpy.ops.curve.switch_direction() bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.convert(target='MESH', keep_original=True) arch_mesh = context.object #now the mesh conversion arch_len = 0 mx = arch_mesh.matrix_world #do some calcs to the curve #TODO: split this method off. It may already #be in odcutils. occ_dir = Vector( (0, 0, 0)) #this will end be a normalized, global direction for i in range(0, len(arch_mesh.data.vertices) - 1): v0 = arch_mesh.data.vertices[i] v1 = arch_mesh.data.vertices[i + 1] V0 = mx * v1.co - mx * v0.co arch_len += V0.length if i < len(arch_mesh.data.vertices) - 2: v2 = arch_mesh.data.vertices[i + 2] V1 = mx * v2.co - mx * v1.co occ_dir += V0.cross(V1) if debug: print("arch is %f mm long" % arch_len) #pull values from the tooth size/data #if we are mirroring, we need to do some logic if mirror: if sextant not in ["UR", "UL", "LR", "LL", "MAX", "MAND"]: print('Incorrect sextant for mirroring') return {'CANCELLED'} else: if sextant.startswith("U"): sextant = "MAX" elif sextant.startswith("L"): sextant = "MAND" #else..leave quadrant alone curve_teeth = quadrant_dict[sextant] occ_dir *= occ_direct_dict[sextant] * 1 / (len(arch_mesh.data.vertices) - 2) occ_dir.normalize() #this deletes the arch mesh...not the arch curve bpy.ops.object.delete() if reorient: arch_z = mx.to_quaternion() * Vector((0, 0, 1)) arch_z.normalize() if math.pow(arch_z.dot(occ_dir), 2) < .9: orient = odcutils.rot_between_vecs( Vector((0, 0, 1)), occ_dir ) #align the local Z of bezier with occlusal direction (which is global). odcutils.reorient_object(arch, orient) if debug: print("working on these teeth %s" % ":".join(curve_teeth)) #import/link teeth from the library restorations = [] if link and len(context.scene.odc_teeth): for tooth in context.scene.odc_teeth: if tooth.name[0:2] in curve_teeth: #TODO: restoration etc? #we will have to check later if we need to use the restoration #from this tooth restorations.append(tooth.name) if debug: print( "These restorations are already in the proposed quadrant %s" % ", ".join(restorations)) #figure out which objects we are going to distribute. lib_teeth_names = odcutils.obj_list_from_lib( tooth_library) #TODO: check if tooth_library is valid? tooth_objects = [[None]] * len( curve_teeth ) #we want this list to be mapped to curve_teeth with it's index...dictionary if we have to delete_later = [] for i, planned_tooth in enumerate(curve_teeth): #this will be a one item list tooth_in_scene = [ tooth for tooth in context.scene.odc_teeth if tooth.name.startswith(planned_tooth) ] if link and len(tooth_in_scene): #check if the restoration is already there...if so, use it if tooth_in_scene[0].contour: tooth_objects[i] = bpy.data.objects[tooth_in_scene[0].contour] #if it's not there, add it in, and associate it with ODCTooth Object else: for tooth in lib_teeth_names: if tooth.startswith( planned_tooth ): #necessary that the planned teeth have logical names new_name = tooth + "_ArchPlanned" if new_name in bpy.data.objects: ob = bpy.data.objects[new_name] me = ob.data ob.user_clear() bpy.data.objects.remove(ob) bpy.data.meshes.remove(me) context.scene.update() odcutils.obj_from_lib(tooth_library, tooth) ob = bpy.data.objects[tooth] context.scene.objects.link(ob) ob.name = new_name tooth_objects[i] = ob tooth_in_scene[0].contour = ob.name break #in case there are multiple copies? else: #the tooth is not existing restoration, and we want to put it in anyway for tooth in lib_teeth_names: if tooth.startswith(planned_tooth): new_name = tooth + "_ArchPlanned" if new_name in bpy.data.objects: ob = bpy.data.objects[new_name] me = ob.data ob.user_clear() bpy.data.objects.remove(ob) bpy.data.meshes.remove(me) context.scene.update() odcutils.obj_from_lib(tooth_library, tooth) ob = bpy.data.objects[tooth] ob.name += "_ArchPlanned" if limit: context.scene.objects.link(ob) delete_later.append(ob) else: context.scene.objects.link(ob) tooth_objects[i] = ob break if debug: print(tooth_objects) #secretly, we imported the whole quadrant..we will delete them later teeth_len = 0 lengths = [[0]] * len(curve_teeth) #list of tooth mesial/distal lengths locs = [[0]] * len(curve_teeth) #normalized list of locations for i, ob in enumerate(tooth_objects): lengths[i] = ob.dimensions[0] teeth_len += ob.dimensions[0] locs[i] = teeth_len - ob.dimensions[0] / 2 scale = arch_len / teeth_len crowding = teeth_len - arch_len if debug > 1: print(lengths) print(locs) print(scale) print("there is %d mm of crowding" % round(crowding, 2)) print("there is a %d pct archlength discrepancy" % round(100 - scale * 100, 2)) #scale them to the right size for i, ob in enumerate(tooth_objects): if shift == 'FOSSA': delta = .05 else: delta = 0 #resize it ob.scale[0] *= scale + delta ob.scale[1] *= scale + delta ob.scale[2] *= scale + delta #find the location of interest we want? # bbox center, cusp tip? fossa/grove, incisal edge? #TODO: odcutils.tooth_features(tooth,feature) (world coords or local?) ob.location = Vector((0, 0, 0)) if ob.rotation_mode != 'QUATERNION': ob.rotation_mode = 'QUATERNION' ob.rotation_quaternion = Quaternion((1, 0, 0, 0)) #center line...we want palatinal face median point z,y with midpointx and center line min local z #buccal line...we want incisal edge median local y, maxlocal z, midpoing bbox x and buccal cusp max z? context.scene.objects.active = ob ob.select = True ob.hide = False ob.constraints.new('FOLLOW_PATH') path_constraint = ob.constraints["Follow Path"] path_constraint.target = arch path_constraint.use_curve_follow = True #find out if we cross the midline if sextant in ['MAX', 'MAND', 'UA', 'LA']: path_constraint.forward_axis = 'FORWARD_X' if int(curve_teeth[i]) > 20 and int(curve_teeth[i]) < 40: path_constraint.forward_axis = 'TRACK_NEGATIVE_X' else: path_constraint.forward_axis = 'FORWARD_X' path_constraint.offset = 100 * (-1 + locs[i] / teeth_len) #after arranging them on the curve, make a 2nd pass to spin them or not #decrease in number means mesial. Except at midline.this will happen #we have constructed curve_teeth such that there will never be a non #integer change in adjacent list members. #eg, #quaternion rotation rules # Qtotal = Qa * Qb represtnts rotation b followed by rotation a #what we are doing is testing the occlusal direction of one tooth vs the arch occlusal direction context.scene.update() ob_dist = tooth_objects[1] ob_mes = tooth_objects[0] mesial = int(curve_teeth[1]) - int( curve_teeth[0]) == 1 #if true....distal numbers > mesial numbers vect = ob_mes.matrix_world * ob_mes.location - ob_dist.matrix_world * ob_dist.location spin = (vect.dot(ob_dist.matrix_world.to_quaternion() * Vector( (1, 0, 0))) < 0) == mesial tooth_occ = ob_mes.matrix_world.to_quaternion() * Vector((0, 0, 1)) flip = tooth_occ.dot(occ_dir) > 0 if debug: print('We will flip the teeth: %s. We will spin the teeth: %s.' % (str(flip), str(spin))) for ob in tooth_objects: if flip: ob.rotation_quaternion = Quaternion((0, 1, 0, 0)) if spin: ob.rotation_quaternion = Quaternion( (0, 0, 0, 1)) * ob.rotation_quaternion for i, ob in enumerate(tooth_objects): if shift == 'BUCCAL': groups = [ "Incisal Edge", "Distobuccal Cusp", "Mesiobuccal Cusp", "Buccal Cusp" ] inds = [] for vgroup in groups: if vgroup in ob.vertex_groups: inds += odcutils.vert_group_inds_get(context, ob, vgroup) max_z = 0 max_ind = 0 for j in inds: z = ob.data.vertices[j].co[2] if z > max_z: max_ind = j max_z = z tip = ob.data.vertices[max_ind].co tooth_shift = Vector( (0, tip[1] * ob.scale[1], tip[2] * ob.scale[2])) if sextant in [ 'MAX', 'MAND', 'UA', 'LA' ]: #no freakin idea why this is happening, but empirically, it's working tooth_shift[1] *= -1 ob.location += (-1 + 2 * flip) * tooth_shift if shift == 'FOSSA': groups = ["Middle Fissure", "Palatinal Face"] inds = [] for vgroup in groups: if vgroup in ob.vertex_groups and vgroup == "Middle Fissure": inds += odcutils.vert_group_inds_get(context, ob, vgroup) min_z = ob.dimensions[2] min_ind = 0 for j in inds: z = ob.data.vertices[j].co[2] if z < min_z: min_ind = j min_z = z depth = ob.data.vertices[min_ind].co tooth_shift = Vector((0, depth[1] * ob.scale[1], depth[2] * ob.scale[2])) elif vgroup in ob.vertex_groups and vgroup == "Palatinal Face": inds += odcutils.vert_group_inds_get(context, ob, vgroup) mx = Matrix.Identity(4) com = odcutils.get_com(ob.data, inds, mx) tooth_shift = odcutils.scale_vec_mult( com, ob.matrix_world.to_scale()) if sextant in [ 'MAX', 'MAND', 'UA', 'LA' ]: #no freakin idea why this is happening, but empirically, it's working tooth_shift[1] *= -1 ob.location += (-1 + 2 * flip) * tooth_shift if limit: bpy.ops.object.select_all(action='DESELECT') for ob in delete_later: ob.select = True context.scene.objects.active = ob bpy.ops.object.delete()