def invoke(self,context,event): self.objs.clear() #here we grab the asset library from the addon prefs settings = get_settings() libpath = settings.imp_lib assets = odcutils.obj_list_from_lib(libpath, exclude = '_') for asset_object_name in assets: self.objs.add().name = asset_object_name #context.window_manager.invoke_search_popup(self.ob_list) context.window_manager.invoke_search_popup(self) return {'FINISHED'}
def invoke(self, context, event): self.objs.clear() settings = get_settings() libpath = settings.drill_lib assets = odcutils.obj_list_from_lib(libpath, include = 'Drill', exclude = 'Sleeve') for asset_object_name in assets: self.objs.add().name = asset_object_name context.window_manager.invoke_search_popup(self) return {'FINISHED'}
def invoke(self, context, event): self.objs.clear() settings = get_settings() libpath = settings.ortho_lib assets = obj_list_from_lib(libpath) for asset_object_name in assets: self.objs.add().name = asset_object_name context.window_manager.invoke_search_popup(self) return {'FINISHED'}
def update_link_operators(): #unregister get crown bpy.utils.unregister_class(OPENDENTAL_OT_place_implant) bpy.utils.unregister_class(OPENDENTAL_OT_implant_from_contour) #redefinte list from user prefs prop global lib_teeth_enum global lib_teeth lib_teeth_enum = [] lib_teeth = [] settings = get_settings() dbg = settings.debug lib_teeth = odcutils.obj_list_from_lib(settings.tooth_lib, exclude = '_', debug = dbg) for ind, obj in enumerate(lib_teeth): lib_teeth_enum.append((str(ind), obj, str(ind))) #reregister. bpy.utils.register_class(OPENDENTAL_OT_place_implant) bpy.utils.register_class(OPENDENTAL_OT_implant_from_contour)
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 place_implant(context, implant_space, location,orientation,imp, hardware = True): ''' args: context implant_space - ODC Implant Space type location - Vector orientation - Matrix or Quaternion lib_implants - imp - string representing implant object name in link library ''' #check if space already has an implant object. #if so, delete, replace, print warning sce = context.scene if implant_space.implant and implant_space.implant in bpy.data.objects: print("replacing the existing implant with the one you chose") Implant = bpy.data.objects[implant_space.implant] #unlink it from the scene, clear it's useres, remove it. if Implant.children: for child in Implant.children: sce.objects.unlink(child) child.user_clear bpy.data.objects.remove(child) sce.objects.unlink(Implant) implant_mesh = Implant.data Implant.user_clear() #remove the object bpy.data.objects.remove(Implant) implant_mesh.user_clear() bpy.data.meshes.remove(implant_mesh) sce.update() #TDOD what about the children/hardwares? world_mx = Matrix.Identity(4) world_mx[0][3]=location[0] world_mx[1][3]=location[1] world_mx[2][3]=location[2] #mx_b = Matrix.Identity(4) #mx_l = Matrix.Identity(4) #is this more memory friendly than listing all objects? current_obs = [ob.name for ob in bpy.data.objects] #link the new implant from the library settings = get_settings() odcutils.obj_from_lib(settings.imp_lib, imp) #this is slightly more robust than trusting we don't have duplicate names. for ob in bpy.data.objects: if ob.name not in current_obs: Implant = ob sce.objects.link(Implant) #Implant.matrix_basis = mx_b Implant.matrix_world = world_mx Implant.update_tag() sce.update() Implant.rotation_mode = 'QUATERNION' Implant.rotation_quaternion = orientation sce.update() #Implant.matrix_local = mx_l #Implant.location = L if sce.odc_props.master: Master = bpy.data.objects[sce.odc_props.master] odcutils.parent_in_place(Implant, Master) else: print('No Master Model, placing implant anyway, moving objects may not preserve spatial relationships') #looks a little redundant, but it ensure if any #duplicates exist our referencing stays accurate Implant.name = implant_space.name + "_" + Implant.name implant_space.implant = Implant.name if hardware: current_obs = [ob.name for ob in bpy.data.objects] inc = imp + '_' settings = get_settings() hardware_list = odcutils.obj_list_from_lib(settings.imp_lib, include = inc) print(hardware_list) for ob in hardware_list: odcutils.obj_from_lib(settings.imp_lib,ob) for ob in bpy.data.objects: if ob.name not in current_obs: sce.objects.link(ob) ob.parent = Implant ob.layers[11] = True #TODO: put this in layer management. return Implant
def execute(self, context): settings = get_settings() dbg = settings.debug #if bpy.context.mode != 'OBJECT': # bpy.ops.object.mode_set(mode = 'OBJECT') sce = context.scene #n = sce.odc_implant_index #implant_space = sce.odc_implants[n] implants = odcutils.implant_selection(context) layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] if implants != []: for implant_space in implants: #check if space already has an implant object. #if so, delete, replace, print warning if implant_space.implant and implant_space.implant in bpy.data.objects: self.report({ 'WARNING' }, "replacing the existing implant with the one you chose") Implant = bpy.data.objects[implant_space.implant] #the origin/location of the implant is it's apex L = Implant.location.copy() world_mx = Implant.matrix_world.copy() #the platorm is the length of the implant above the apex, in the local Z direction #local Z positive is out the apex, soit's negative. #Put the cursor there sce.cursor_location = L - Implant.dimensions[ 2] * world_mx.to_3x3() * Vector((0, 0, 1)) #first get rid of children...so we can use the #parent to find out who the children are if Implant.children: for child in Implant.children: sce.objects.unlink(child) child.user_clear() bpy.data.objects.remove(child) #unlink it from the scene, clear it's users, remove it. sce.objects.unlink(Implant) Implant.user_clear() #remove the object bpy.data.objects.remove(Implant) #TDOD what about the children/hardwares? else: world_mx = Matrix.Identity(4) world_mx[0][3] = sce.cursor_location[0] world_mx[1][3] = sce.cursor_location[1] world_mx[2][3] = sce.cursor_location[2] #is this more memory friendly than listing all objects? current_obs = [ob.name for ob in bpy.data.objects] #link the new implant from the library odcutils.obj_from_lib(settings.imp_lib, self.imp) #this is slightly more robust than trusting we don't have duplicate names. for ob in bpy.data.objects: if ob.name not in current_obs: Implant = ob sce.objects.link(Implant) #this relies on the associated hardware objects having the parent implant #name inside them if self.hardware: current_obs = [ob.name for ob in bpy.data.objects] inc = self.imp + '_' hardware_list = odcutils.obj_list_from_lib( settings.imp_lib, include=inc) print(hardware_list) for ob in hardware_list: odcutils.obj_from_lib(settings.imp_lib, ob) for ob in bpy.data.objects: if ob.name not in current_obs: sce.objects.link(ob) ob.parent = Implant ob.layers[11] = True delta = Implant.dimensions[2] * world_mx.to_3x3() * Vector( (0, 0, 1)) print(delta.length) world_mx[0][3] += delta[0] world_mx[1][3] += delta[1] world_mx[2][3] += delta[2] Implant.matrix_world = world_mx if sce.odc_props.master: Master = bpy.data.objects[sce.odc_props.master] odcutils.parent_in_place(Implant, Master) else: self.report({ 'WARNING' }, 'No Master Model, placing implant anyway, moving objects may not preserve spatial relationships' ) #looks a little redundant, but it ensure if any #duplicates exist our referencing stays accurate Implant.name = implant_space.name + '_' + Implant.name implant_space.implant = Implant.name odcutils.layer_management(sce.odc_implants, debug=dbg) else: world_mx = Matrix.Identity(4) world_mx[0][3] = sce.cursor_location[0] world_mx[1][3] = sce.cursor_location[1] world_mx[2][3] = sce.cursor_location[2] #is this more memory friendly than listing all objects? current_obs = [ob.name for ob in bpy.data.objects] #link the new implant from the library odcutils.obj_from_lib(settings.imp_lib, self.imp) #this is slightly more robust than trusting we don't have duplicate names. for ob in bpy.data.objects: if ob.name not in current_obs: Implant = ob sce.objects.link(Implant) Implant.matrix_world = world_mx 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 #if bpy.context.mode != 'OBJECT': # bpy.ops.object.mode_set(mode = 'OBJECT') sce = context.scene n = sce.odc_implant_index implant_space = sce.odc_implants[n] implants = odcutils.implant_selection(context) layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] if implants != []: for implant_space in implants: #check if space already has an implant object. #if so, delete, replace, print warning if implant_space.implant and implant_space.implant in bpy.data.objects: self.report({'WARNING'}, "replacing the existing implant with the one you chose") Implant = bpy.data.objects[implant_space.implant] #the origin/location of the implant is it's apex L = Implant.location.copy() world_mx = Implant.matrix_world.copy() #the platorm is the length of the implant above the apex, in the local Z direction #local Z positive is out the apex, soit's negative. #Put the cursor there sce.cursor_location = L - Implant.dimensions[2] * world_mx.to_3x3() * Vector((0,0,1)) #first get rid of children...so we can use the #parent to find out who the children are if Implant.children: for child in Implant.children: sce.objects.unlink(child) child.user_clear() bpy.data.objects.remove(child) #unlink it from the scene, clear it's users, remove it. sce.objects.unlink(Implant) Implant.user_clear() #remove the object bpy.data.objects.remove(Implant) #TDOD what about the children/hardwares? else: world_mx = Matrix.Identity(4) world_mx[0][3]=sce.cursor_location[0] world_mx[1][3]=sce.cursor_location[1] world_mx[2][3]=sce.cursor_location[2] #is this more memory friendly than listing all objects? current_obs = [ob.name for ob in bpy.data.objects] #link the new implant from the library odcutils.obj_from_lib(settings.imp_lib,self.imp) #this is slightly more robust than trusting we don't have duplicate names. for ob in bpy.data.objects: if ob.name not in current_obs: Implant = ob sce.objects.link(Implant) #this relies on the associated hardware objects having the parent implant #name inside them if self.hardware: current_obs = [ob.name for ob in bpy.data.objects] inc = self.imp + '_' hardware_list = odcutils.obj_list_from_lib(settings.imp_lib, include = inc) print(hardware_list) for ob in hardware_list: odcutils.obj_from_lib(settings.imp_lib,ob) for ob in bpy.data.objects: if ob.name not in current_obs: sce.objects.link(ob) ob.parent = Implant ob.layers[11] = True delta = Implant.dimensions[2] * world_mx.to_3x3() * Vector((0,0,1)) print(delta.length) world_mx[0][3] += delta[0] world_mx[1][3] += delta[1] world_mx[2][3] += delta[2] Implant.matrix_world = world_mx if sce.odc_props.master: Master = bpy.data.objects[sce.odc_props.master] odcutils.parent_in_place(Implant, Master) else: self.report({'WARNING'}, 'No Master Model, placing implant anyway, moving objects may not preserve spatial relationships') #looks a little redundant, but it ensure if any #duplicates exist our referencing stays accurate Implant.name = implant_space.name + '_' + Implant.name implant_space.implant = Implant.name odcutils.layer_management(sce.odc_implants, debug = dbg) 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()