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 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 obj_from_lib(settings.ortho_lib, self.ob) #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: Bracket = ob sce.objects.link(Bracket) rv3d = context.region_data view_mx = rv3d.view_rotation.to_matrix() Bracket.matrix_world = world_mx * view_mx.to_4x4() 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 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 obj_from_lib(settings.ortho_lib,self.ob) #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: Bracket = ob sce.objects.link(Bracket) rv3d = context.region_data view_mx = rv3d.view_rotation.to_matrix() Bracket.matrix_world = world_mx * view_mx.to_4x4() return {'FINISHED'}
def invoke(self, context, event): settings = get_settings() libpath = settings.ortho_lib assets = obj_list_from_lib(libpath) if settings.bracket in assets: current_obs = [ob.name for ob in bpy.data.objects] obj_from_lib(settings.ortho_lib, settings.bracket) for ob in bpy.data.objects: if ob.name not in current_obs: Bracket = ob Bracket.hide = False context.scene.objects.link(Bracket) else: Bracket = None if context.object and context.object.type == 'MESH': self.bracket_manager = BracketDataManager( context, snap_type='OBJECT', snap_object=context.object, name='Bracket', bracket=Bracket) self.bracket_slicer = BracektSlicer(context, self.bracket_manager) else: self.bracket_manager = BracketDataManager(context, snap_type='SCENE', snap_object=None, name='Bracket', bracket=Bracket) self.bracket_slicer = None help_txt = "DRAW MARGIN OUTLINE\n\nLeft Click on model to place bracket.\n G to grab \n S to show slice \n ENTER to confirm \n ESC to cancel" self.help_box = TextBox(context, 500, 500, 300, 200, 10, 20, help_txt) self.help_box.snap_to_corner(context, corner=[1, 1]) self.mode = 'start' self._handle = bpy.types.SpaceView3D.draw_handler_add( bracket_placement_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}
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 implants = odcutils.implant_selection(context) layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True if implants != []: for implant_space in implants: #check if space already has an implant object. #if so, delete, replace, print warning Implant = bpy.data.objects[implant_space.implant] if Implant.rotation_mode != 'QUATERNION': Implant.rotation_mode = 'QUATERNION' Implant.update_tag() sce.update() if bpy.data.objects.get(implant_space.drill): self.report( {'WARNING'}, "replacing the existing drill with the one you chose") Sleeve = bpy.data.objects[implant_space.drill] #unlink it from the scene, clear it's users, remove it. sce.objects.unlink(Sleeve) Implant.user_clear() #remove the object bpy.data.objects.remove(Sleeve) 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.drill_lib, self.drill) #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: Sleeve = ob sce.objects.link(Sleeve) Sleeve.layers[19] = True mx_w = Implant.matrix_world.copy() #point the right direction Sleeve.rotation_mode = 'QUATERNION' Sleeve.rotation_quaternion = mx_w.to_quaternion() Sleeve.update_tag() context.scene.update() Trans = Sleeve.rotation_quaternion * Vector( (0, 0, -self.depth)) Sleeve.matrix_world[0][3] = mx_w[0][3] + Trans[0] Sleeve.matrix_world[1][3] = mx_w[1][3] + Trans[1] Sleeve.matrix_world[2][3] = mx_w[2][3] + Trans[2] Sleeve.name = implant_space.name + '_' + Sleeve.name implant_space.drill = Sleeve.name Sleeve.update_tag() context.scene.update() odcutils.parent_in_place(Sleeve, Implant) odcutils.layer_management(sce.odc_implants, debug=dbg) for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[19] = 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()
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 implants = odcutils.implant_selection(context) layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True if implants != []: for implant_space in implants: #check if space already has an implant object. #if so, delete, replace, print warning Implant = bpy.data.objects[implant_space.implant] if Implant.rotation_mode != 'QUATERNION': Implant.rotation_mode = 'QUATERNION' Implant.update_tag() sce.update() if bpy.data.objects.get(implant_space.drill): self.report({'WARNING'}, "replacing the existing drill with the one you chose") Sleeve = bpy.data.objects[implant_space.drill] #unlink it from the scene, clear it's users, remove it. sce.objects.unlink(Sleeve) Implant.user_clear() #remove the object bpy.data.objects.remove(Sleeve) 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.drill_lib,self.drill) #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: Sleeve = ob sce.objects.link(Sleeve) Sleeve.layers[19] = True mx_w = Implant.matrix_world.copy() #point the right direction Sleeve.rotation_mode = 'QUATERNION' Sleeve.rotation_quaternion = mx_w.to_quaternion() Sleeve.update_tag() context.scene.update() Trans = Sleeve.rotation_quaternion * Vector((0,0,-self.depth)) Sleeve.matrix_world[0][3] = mx_w[0][3] + Trans[0] Sleeve.matrix_world[1][3] = mx_w[1][3] + Trans[1] Sleeve.matrix_world[2][3] = mx_w[2][3] + Trans[2] Sleeve.name = implant_space.name + '_' + Sleeve.name implant_space.drill = Sleeve.name Sleeve.update_tag() context.scene.update() odcutils.parent_in_place(Sleeve, Implant) odcutils.layer_management(sce.odc_implants, debug = dbg) for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[19] = 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()