Пример #1
0
 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'}
Пример #2
0
 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'}
Пример #3
0
 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'}
Пример #4
0
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)
Пример #5
0
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()        
Пример #6
0
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
Пример #7
0
    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'}
Пример #8
0
    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()