Example #1
0
 def execute(self,context):
     settings = get_settings()
     dbg = settings.debug
     #TODO: Scene Preservation recording
     teeth = odcutils.tooth_selection(context)
     sce = bpy.context.scene
     
     layers_copy = [layer for layer in context.scene.layers]
     context.scene.layers[0] 
     
     for tooth in teeth:
         
         #see if there is a corresponding implant
         if tooth.name in sce.odc_implants:
             contour = bpy.data.objects.get(tooth.contour)
             Z  = Vector((0,0,-1))
             if contour:
                 
                 if tooth.axis:
                     Axis = bpy.data.objects.get(tooth.axis)
                     if Axis:
                         neg_z = Axis.matrix_world.to_quaternion() * Z
                         rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z)
                     else:
                         neg_z = contour.matrix_world.to_quaternion() * Z
                         rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z)
                 else:
                     neg_z = contour.matrix_world.to_quaternion() * Z
                     rot_diff = odcutils.rot_between_vecs(Vector((0,0,1)), neg_z)
                 mx = contour.matrix_world
                 x = mx[0][3]
                 y = mx[1][3]
                 z = mx[2][3]
                 
                 #CEJ Location
                 new_loc = odcutils.box_feature_locations(contour, Vector((0,0,-1)))
                 
                 Imp = implant_utils.place_implant(context, sce.odc_implants[tooth.name], new_loc, rot_diff, self.imp, hardware = self.hardware)
                 
                 #reposition platform below CEJ
                 world_mx = Imp.matrix_world
                 delta =  Imp.dimensions[2] * world_mx.to_3x3() * Vector((0,0,1)) + self.depth * world_mx.to_3x3() * Vector((0,0,1))
                 
                 world_mx[0][3] += delta[0]
                 world_mx[1][3] += delta[1]
                 world_mx[2][3] += delta[2]
                 #odcutils.reorient_object(Imp, rot_diff)
     
     odcutils.layer_management(sce.odc_implants, debug = False)
     for i, layer in enumerate(layers_copy):
         context.scene.layers[i] = layer
     context.scene.layers[11] = True
     
     return {'FINISHED'}
Example #2
0
    def execute(self, context):
        settings = get_settings()
        dbg = settings.debug
        #TODO: Scene Preservation recording
        teeth = odcutils.tooth_selection(context)
        sce = bpy.context.scene

        layers_copy = [layer for layer in context.scene.layers]
        context.scene.layers[0]

        for tooth in teeth:

            #see if there is a corresponding implant
            if tooth.name in sce.odc_implants:
                contour = bpy.data.objects.get(tooth.contour)
                Z = Vector((0, 0, -1))
                if contour:

                    if tooth.axis:
                        Axis = bpy.data.objects.get(tooth.axis)
                        if Axis:
                            neg_z = Axis.matrix_world.to_quaternion() * Z
                            rot_diff = odcutils.rot_between_vecs(
                                Vector((0, 0, 1)), neg_z)
                        else:
                            neg_z = contour.matrix_world.to_quaternion() * Z
                            rot_diff = odcutils.rot_between_vecs(
                                Vector((0, 0, 1)), neg_z)
                    else:
                        neg_z = contour.matrix_world.to_quaternion() * Z
                        rot_diff = odcutils.rot_between_vecs(
                            Vector((0, 0, 1)), neg_z)
                    mx = contour.matrix_world
                    x = mx[0][3]
                    y = mx[1][3]
                    z = mx[2][3]

                    #CEJ Location
                    new_loc = odcutils.box_feature_locations(
                        contour, Vector((0, 0, -1)))

                    Imp = implant_utils.place_implant(
                        context,
                        sce.odc_implants[tooth.name],
                        new_loc,
                        rot_diff,
                        self.imp,
                        hardware=self.hardware)

                    #reposition platform below CEJ
                    world_mx = Imp.matrix_world
                    delta = Imp.dimensions[2] * world_mx.to_3x3() * Vector(
                        (0, 0, 1)) + self.depth * world_mx.to_3x3() * Vector(
                            (0, 0, 1))

                    world_mx[0][3] += delta[0]
                    world_mx[1][3] += delta[1]
                    world_mx[2][3] += delta[2]
                    #odcutils.reorient_object(Imp, rot_diff)

        odcutils.layer_management(sce.odc_implants, debug=False)
        for i, layer in enumerate(layers_copy):
            context.scene.layers[i] = layer
        context.scene.layers[11] = True

        return {'FINISHED'}
def teeth_to_curve(context, arch, sextant, tooth_library, teeth = [], shift = 'BUCCAL', limit = False, link = False, reverse = False, mirror = False, debug = False, reorient = True):
    '''
    puts teeth along a curve for full arch planning
    args:
       curve - blender Curve object
       sextant - the quadrant or sextant that the curve corresponds to. enum in 'MAX', 'MAND', 'UR' 'LR' 'LR' 'LL' 'UA' 'LA' '
       teeth - list of odc_teeth, to link to or from.  eg, if tooth already
         a restoration it will use that object, if not, it will link a new
         blender object to that tooth as the restoration or contour.
       shift = whether to use buccal cusps, center of mass or, center of fossa to align onto cirve.  enum in 'BUCCAL', 'COM', 'FOSSA'
       limit - only link teeth for each tooth in teeth
       link - Bool, whether or not to link to/from the teeth list
    '''
    if debug:
        start = time.time()
        
    orig_arch_name = arch.name
    
    bpy.ops.object.select_all(action='DESELECT')
    context.scene.objects.active = arch
    arch.hide = False
    arch.select = True
    
    if mirror:
        #This should help with the mirroring?
        arch.data.resolution_u = 5 
        
        #if it doesn't have a mirror, we need to mirror it
        if "Mirror" not in arch.modifiers:
            bpy.ops.object.modifier_add(type='MIRROR')
        #non mirrored curve needed for appropriate constraining..
        #convert to mesh applies mirror, reconvert to curve gives us a full length curve
        arch.modifiers["Mirror"].merge_threshold = 5
        bpy.ops.object.convert(target='MESH',keep_original = True)
        bpy.ops.object.convert(target='CURVE', keep_original = False) #this will be the new full arch        
        arch = context.object
        arch.name = orig_arch_name + "_Mirrored"
    

    
    #we may want to switch the direction of the curve :-)
    #we may also want to handle this outside of this function
    if reverse:
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.curve.switch_direction()
        bpy.ops.object.mode_set(mode='OBJECT')
        
    bpy.ops.object.convert(target='MESH', keep_original = True)
    arch_mesh = context.object #now the mesh conversion
    arch_len = 0
    mx = arch_mesh.matrix_world
    
    #do some calcs to the curve
    #TODO:  split this method off.  It may already
    #be in odcutils.
    occ_dir = Vector((0,0,0))  #this will end  be a normalized, global direction
    for i in range(0,len(arch_mesh.data.vertices)-1):
        v0 = arch_mesh.data.vertices[i]
        v1 = arch_mesh.data.vertices[i+1]
        V0 = mx*v1.co - mx*v0.co
        arch_len += V0.length
    
        if i < len(arch_mesh.data.vertices)-2:
            v2 = arch_mesh.data.vertices[i+2]
            V1 = mx*v2.co - mx*v1.co
            
            occ_dir += V0.cross(V1)
    
    if debug:
        print("arch is %f mm long" % arch_len)

    #pull values from the tooth size/data
        #if we are mirroring, we need to do some logic
    if mirror:
        if sextant not in ["UR","UL","LR","LL","MAX","MAND"]:
            print('Incorrect sextant for mirroring')
            return {'CANCELLED'}
        else:
            if sextant.startswith("U"):
                sextant = "MAX"
            elif sextant.startswith("L"):
                sextant = "MAND"
            #else..leave quadrant alone
            
    curve_teeth = quadrant_dict[sextant]
    occ_dir *= occ_direct_dict[sextant] * 1/(len(arch_mesh.data.vertices)-2)
    occ_dir.normalize()
    
    #this deletes the arch mesh...not the arch curve
    bpy.ops.object.delete()
    
    if reorient:
        arch_z = mx.to_quaternion() * Vector((0,0,1))
        arch_z.normalize()
        if math.pow(arch_z.dot(occ_dir),2) < .9:
            orient = odcutils.rot_between_vecs(Vector((0,0,1)), occ_dir) #align the local Z of bezier with occlusal direction (which is global).
            odcutils.reorient_object(arch, orient)
    if debug:
        print("working on these teeth %s" % ":".join(curve_teeth))
    
    #import/link teeth from the library
    restorations = []
    if link and len(context.scene.odc_teeth): 
        for tooth in context.scene.odc_teeth:
            if tooth.name[0:2] in curve_teeth:
                #TODO: restoration etc?
                #we will have to check later if we need to use the restoration
                #from this tooth
                restorations.append(tooth.name)
        if debug:               
            print("These restorations are already in the proposed quadrant %s" % ", ".join(restorations))
            
    
    #figure out which objects we are going to distribute.
    lib_teeth_names = odcutils.obj_list_from_lib(tooth_library) #TODO: check if tooth_library is valid?
    tooth_objects=[[None]]*len(curve_teeth) #we want this list to be mapped to curve_teeth with it's index...dictionary if we have to
    delete_later = []
    for i, planned_tooth in enumerate(curve_teeth):
        #this will be a one item list
        tooth_in_scene = [tooth for tooth in context.scene.odc_teeth if tooth.name.startswith(planned_tooth)]
        if link and len(tooth_in_scene):
            #check if the restoration is already there...if so, use it
            if tooth_in_scene[0].contour:
                tooth_objects[i] = bpy.data.objects[tooth_in_scene[0].contour]
        
            #if it's not there, add it in, and associate it with ODCTooth Object
            else:
                for tooth in lib_teeth_names:
                    if tooth.startswith(planned_tooth):  #necessary that the planned teeth have logical names
                        
                        new_name = tooth + "_ArchPlanned"
                        if new_name in bpy.data.objects:   
                            ob = bpy.data.objects[new_name]
                            me = ob.data
                            ob.user_clear()
                            bpy.data.objects.remove(ob)
                            bpy.data.meshes.remove(me)
                            context.scene.update()
                           
                        odcutils.obj_from_lib(tooth_library, tooth)
                        ob = bpy.data.objects[tooth]
                        context.scene.objects.link(ob)
                        ob.name = new_name
                        tooth_objects[i] = ob
                        tooth_in_scene[0].contour = ob.name
                        break #in case there are multiple copies?
        else: #the tooth is not existing restoration, and we want to put it in anyway
            for tooth in lib_teeth_names:
                if tooth.startswith(planned_tooth):
                    new_name = tooth + "_ArchPlanned"
                    if new_name in bpy.data.objects:   
                        ob = bpy.data.objects[new_name]
                        me = ob.data
                        ob.user_clear()
                        bpy.data.objects.remove(ob)
                        bpy.data.meshes.remove(me)
                        context.scene.update()
                        
                    odcutils.obj_from_lib(tooth_library, tooth)
                    ob = bpy.data.objects[tooth]
                    ob.name += "_ArchPlanned"
                    if limit:
                        context.scene.objects.link(ob)
                        delete_later.append(ob)    
                    else:
                        context.scene.objects.link(ob)
                        
                    tooth_objects[i]= ob
                    break  
    if debug:
        print(tooth_objects)
    
    #secretly, we imported the whole quadrant..we will delete them later
    teeth_len = 0
    lengths = [[0]] * len(curve_teeth) #list of tooth mesial/distal lengths
    locs = [[0]] * len(curve_teeth) #normalized list of locations
    for i, ob in enumerate(tooth_objects):
        lengths[i] = ob.dimensions[0]
        
        teeth_len += ob.dimensions[0]
        locs[i] = teeth_len - ob.dimensions[0]/2
    scale = arch_len/teeth_len
    crowding = teeth_len - arch_len
    
    if debug > 1:
        print(lengths)
        print(locs)
        print(scale)
        print("there is %d mm of crowding" % round(crowding,2))
        print("there is a %d pct archlength discrepancy" % round(100-scale*100, 2))
       
    #scale them to the right size
    for i, ob in enumerate(tooth_objects):
        
        if shift == 'FOSSA':
            delta = .05
        else:
            delta = 0
        #resize it
        ob.scale[0] *= scale + delta
        ob.scale[1] *= scale + delta
        ob.scale[2] *= scale + delta
        
    
        #find the location of interest we want?
        # bbox center, cusp tip? fossa/grove, incisal edge?
        #TODO:  odcutils.tooth_features(tooth,feature)  (world coords or local?)
        ob.location = Vector((0,0,0))
        if ob.rotation_mode != 'QUATERNION':
            ob.rotation_mode = 'QUATERNION'
        
        ob.rotation_quaternion = Quaternion((1,0,0,0))
            
        #center line...we want palatinal face median point z,y with midpointx and center line min local z
        #buccal line...we want incisal edge median local y, maxlocal z, midpoing bbox x and buccal cusp max z?

        context.scene.objects.active = ob
        ob.select = True
        ob.hide = False

        ob.constraints.new('FOLLOW_PATH')
        path_constraint = ob.constraints["Follow Path"]
        path_constraint.target = arch
        path_constraint.use_curve_follow = True
        #find out if we cross the midline
        if sextant in ['MAX','MAND','UA','LA']:
            path_constraint.forward_axis = 'FORWARD_X'
            if int(curve_teeth[i]) > 20 and int(curve_teeth[i]) < 40:
                path_constraint.forward_axis = 'TRACK_NEGATIVE_X'
        else:
            path_constraint.forward_axis = 'FORWARD_X'

        path_constraint.offset = 100*(-1 + locs[i]/teeth_len)


    #after arranging them on the curve, make a 2nd pass to spin them or not
    
    #decrease in number means mesial.  Except at midline.this will happen
    #we have constructed curve_teeth such that there will never be a non
    #integer change in adjacent list members. #eg, 
    #quaternion rotation rules
    # Qtotal = Qa * Qb represtnts rotation b followed by rotation a
    #what we are doing is testing the occlusal direction of one tooth vs the arch occlusal direction
    context.scene.update()
    ob_dist = tooth_objects[1]
    ob_mes = tooth_objects[0]
    mesial = int(curve_teeth[1]) - int(curve_teeth[0]) == 1 #if true....distal numbers > mesial numbers
    vect = ob_mes.matrix_world * ob_mes.location - ob_dist.matrix_world * ob_dist.location
    spin = (vect.dot(ob_dist.matrix_world.to_quaternion() * Vector((1,0,0))) < 0) == mesial

    tooth_occ = ob_mes.matrix_world.to_quaternion() * Vector((0,0,1))
    flip = tooth_occ.dot(occ_dir) > 0
        
    if debug:
        print('We will flip the teeth: %s. We will spin the teeth: %s.' % (str(flip), str(spin)))
    
    for ob in tooth_objects:
        if flip:
            ob.rotation_quaternion = Quaternion((0,1,0,0))
        if spin:
            ob.rotation_quaternion = Quaternion((0,0,0,1)) * ob.rotation_quaternion 
 
    for i, ob in enumerate(tooth_objects):
                    
            if shift == 'BUCCAL':
                groups = ["Incisal Edge", "Distobuccal Cusp","Mesiobuccal Cusp", "Buccal Cusp"]
                inds = []
                for vgroup in groups:
                    if vgroup in ob.vertex_groups:
                        inds += odcutils.vert_group_inds_get(context, ob, vgroup)
                max_z = 0
                max_ind = 0       
                for j in inds:
                    z = ob.data.vertices[j].co[2]
                    if z > max_z:
                        max_ind = j
                        max_z = z
                tip = ob.data.vertices[max_ind].co
                tooth_shift = Vector((0,tip[1]*ob.scale[1],tip[2]*ob.scale[2]))
                
                if sextant in ['MAX','MAND','UA','LA']: #no freakin idea why this is happening, but empirically, it's working
                    tooth_shift[1]*= -1
                    
                ob.location += (-1 + 2*flip) * tooth_shift
    
    
            if shift == 'FOSSA':
                groups = ["Middle Fissure", "Palatinal Face"]
                inds = []
                for vgroup in groups:
                    if vgroup in ob.vertex_groups and vgroup == "Middle Fissure":
                        inds += odcutils.vert_group_inds_get(context, ob, vgroup)
                
                        min_z = ob.dimensions[2]
                        min_ind = 0       
                        for j in inds:
                            z = ob.data.vertices[j].co[2]
                            if z < min_z:
                                min_ind = j
                                min_z = z
                                depth = ob.data.vertices[min_ind].co
                                tooth_shift = Vector((0,depth[1]*ob.scale[1],depth[2]*ob.scale[2]))
                                
                    elif vgroup in ob.vertex_groups and vgroup  == "Palatinal Face":
                        inds += odcutils.vert_group_inds_get(context, ob, vgroup)
                        mx =  Matrix.Identity(4)
                        com = odcutils.get_com(ob.data, inds, mx)
                        tooth_shift = odcutils.scale_vec_mult(com, ob.matrix_world.to_scale())
                
                if sextant in ['MAX','MAND','UA','LA']: #no freakin idea why this is happening, but empirically, it's working
                    tooth_shift[1]*= -1
                    
                ob.location += (-1 + 2*flip) * tooth_shift
    
    if limit:
        bpy.ops.object.select_all(action='DESELECT')        
        for ob in delete_later:
            ob.select = True
            
        context.scene.objects.active = ob
        bpy.ops.object.delete()        
Example #4
0
def break_contact_deform(context, ob1,ob2, debug = False):
    '''
    separate two objects by deforming a lattice with
    a plane.  Results in a smooth separation.
    
    args:
    
    ret:
    
    '''

    
    if debug:
        print('ob1 name: %s' % ob1.name)
        print('ob2 name: %s' % ob2.name)
    quat_1 = ob1.matrix_world.to_quaternion()
    quat_2 = ob2.matrix_world.to_quaternion()
    
    lat1 = odcutils.bbox_to_lattice(context.scene, ob1)
    lat2 = odcutils.bbox_to_lattice(context.scene, ob2)
    
    print('we made lattices?')
    loc_1 = odcutils.get_bbox_center(ob1, world = True)
    loc_2 = odcutils.get_bbox_center(ob2, world = True)
    diff = loc_2 - loc_1

    
    #the directions to keep things simple.
    x = Vector((1,0,0))
    y = Vector((0,1,0))
    z = Vector((0,0,1))
    vecs = [x,y,z]
    #dot each of the x,y,z coords (transformed to workd dir) with the vector between
    #the two bounding box centers.
    
    dirs1 = [(quat_1 * x).dot(diff)**2, (quat_1 * y).dot(diff)**2, (quat_1 * z).dot(diff)**2]
    dirs2 = [(quat_2 * x).dot(diff)**2, (quat_2 * y).dot(diff)**2, (quat_2 * z).dot(diff)**2]
    
    #find the maximium dot product
    #this is the dirction which is most parallel
    dir1 = dirs1.index(max(dirs1))
    dir2 = dirs2.index(max(dirs2))
    
    #check i we need to negate eithe directions
    #don't get confused because we will negate again
    #when we put the shrinwrap mod on.  This is determinging
    #whether +x or -x points at the othe robject
    neg1 = 1 + -2 * ((quat_1 * vecs[dir1]).dot(diff) < 0)
    neg2 = 1 + -2 * ((quat_2 * vecs[dir2]).dot(diff) > 0)
    
    vec1 = neg1 * vecs[dir1]
    vec2 = neg2 * vecs[dir2]
    
    if debug:
        print(ob1.name + ' is pointed toward ' + ob2.name + ' in the direction:')
        print(vec1)
        print(ob2.name + ' is pointed toward ' + ob1.name + ' in the direction:')
        print(vec2)
    
    pt1 = odcutils.box_feature_locations(ob1, vec1)
    pt2 = odcutils.box_feature_locations(ob2, vec2)
    
    if debug:
        print(pt1)
        print(pt2)
     
    midpoint = .5 * (pt1 + pt2)
    
    pln_verts = [Vector((1,1,0)),Vector((-1,1,0)),Vector((-1,-1,0)),Vector((1,-1,0))]
    pln_faces = [(0,1,2,3)]
    pln_mesh = bpy.data.meshes.new('separator')
    pln_mesh.from_pydata(pln_verts,[],pln_faces)
    new_plane_ob = bpy.data.objects.new('Separator', pln_mesh)
    new_plane_ob.rotation_mode = 'QUATERNION'
    new_plane_ob.rotation_quaternion = odcutils.rot_between_vecs(Vector((0,0,1)), diff)
    new_plane_ob.location = midpoint
    new_plane_ob.scale = .5 * (ob1.dimensions + ob2.dimensions)
    
    context.scene.objects.link(new_plane_ob)
    
    mod1 = lat1.modifiers.new('Contact', 'SHRINKWRAP')
    mod2 = lat2.modifiers.new('Contact', 'SHRINKWRAP')

    mod1.wrap_method = 'PROJECT'
    mod2.wrap_method = 'PROJECT'
    
    if neg1 < 0:
        mod1.use_negative_direction = False
        mod1.use_positive_direction = True
    else:
        mod1.use_negative_direction = True
        mod1.use_positive_direction = False
        
    if neg2 < 0:
        mod2.use_negative_direction = False
        mod2.use_positive_direction = True
    else:
        mod2.use_negative_direction = True
        mod2.use_positive_direction = False
        
        
    if dir1 == 0:
        mod1.use_project_x = True
    elif dir1 == 1:
        mod1.use_project_y = True
    else:
        mod1.use_project_z = True
    
    if dir2 == 0:
        mod2.use_project_x = True
    elif dir2 == 1:
        mod2.use_project_y = True
    else:
        mod2.use_project_z = True
    
    mod1.target = new_plane_ob
    mod2.target = new_plane_ob    
    print('broken!')
def teeth_to_curve(context,
                   arch,
                   sextant,
                   tooth_library,
                   teeth=[],
                   shift='BUCCAL',
                   limit=False,
                   link=False,
                   reverse=False,
                   mirror=False,
                   debug=False,
                   reorient=True):
    '''
    puts teeth along a curve for full arch planning
    args:
       curve - blender Curve object
       sextant - the quadrant or sextant that the curve corresponds to. enum in 'MAX', 'MAND', 'UR' 'LR' 'LR' 'LL' 'UA' 'LA' '
       teeth - list of odc_teeth, to link to or from.  eg, if tooth already
         a restoration it will use that object, if not, it will link a new
         blender object to that tooth as the restoration or contour.
       shift = whether to use buccal cusps, center of mass or, center of fossa to align onto cirve.  enum in 'BUCCAL', 'COM', 'FOSSA'
       limit - only link teeth for each tooth in teeth
       link - Bool, whether or not to link to/from the teeth list
    '''
    if debug:
        start = time.time()

    orig_arch_name = arch.name

    bpy.ops.object.select_all(action='DESELECT')
    context.scene.objects.active = arch
    arch.hide = False
    arch.select = True

    if mirror:
        #This should help with the mirroring?
        arch.data.resolution_u = 5

        #if it doesn't have a mirror, we need to mirror it
        if "Mirror" not in arch.modifiers:
            bpy.ops.object.modifier_add(type='MIRROR')
        #non mirrored curve needed for appropriate constraining..
        #convert to mesh applies mirror, reconvert to curve gives us a full length curve
        arch.modifiers["Mirror"].merge_threshold = 5
        bpy.ops.object.convert(target='MESH', keep_original=True)
        bpy.ops.object.convert(
            target='CURVE',
            keep_original=False)  #this will be the new full arch
        arch = context.object
        arch.name = orig_arch_name + "_Mirrored"

    #we may want to switch the direction of the curve :-)
    #we may also want to handle this outside of this function
    if reverse:
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.curve.switch_direction()
        bpy.ops.object.mode_set(mode='OBJECT')

    bpy.ops.object.convert(target='MESH', keep_original=True)
    arch_mesh = context.object  #now the mesh conversion
    arch_len = 0
    mx = arch_mesh.matrix_world

    #do some calcs to the curve
    #TODO:  split this method off.  It may already
    #be in odcutils.
    occ_dir = Vector(
        (0, 0, 0))  #this will end  be a normalized, global direction
    for i in range(0, len(arch_mesh.data.vertices) - 1):
        v0 = arch_mesh.data.vertices[i]
        v1 = arch_mesh.data.vertices[i + 1]
        V0 = mx * v1.co - mx * v0.co
        arch_len += V0.length

        if i < len(arch_mesh.data.vertices) - 2:
            v2 = arch_mesh.data.vertices[i + 2]
            V1 = mx * v2.co - mx * v1.co

            occ_dir += V0.cross(V1)

    if debug:
        print("arch is %f mm long" % arch_len)

    #pull values from the tooth size/data
    #if we are mirroring, we need to do some logic
    if mirror:
        if sextant not in ["UR", "UL", "LR", "LL", "MAX", "MAND"]:
            print('Incorrect sextant for mirroring')
            return {'CANCELLED'}
        else:
            if sextant.startswith("U"):
                sextant = "MAX"
            elif sextant.startswith("L"):
                sextant = "MAND"
            #else..leave quadrant alone

    curve_teeth = quadrant_dict[sextant]
    occ_dir *= occ_direct_dict[sextant] * 1 / (len(arch_mesh.data.vertices) -
                                               2)
    occ_dir.normalize()

    #this deletes the arch mesh...not the arch curve
    bpy.ops.object.delete()

    if reorient:
        arch_z = mx.to_quaternion() * Vector((0, 0, 1))
        arch_z.normalize()
        if math.pow(arch_z.dot(occ_dir), 2) < .9:
            orient = odcutils.rot_between_vecs(
                Vector((0, 0, 1)), occ_dir
            )  #align the local Z of bezier with occlusal direction (which is global).
            odcutils.reorient_object(arch, orient)
    if debug:
        print("working on these teeth %s" % ":".join(curve_teeth))

    #import/link teeth from the library
    restorations = []
    if link and len(context.scene.odc_teeth):
        for tooth in context.scene.odc_teeth:
            if tooth.name[0:2] in curve_teeth:
                #TODO: restoration etc?
                #we will have to check later if we need to use the restoration
                #from this tooth
                restorations.append(tooth.name)
        if debug:
            print(
                "These restorations are already in the proposed quadrant %s" %
                ", ".join(restorations))

    #figure out which objects we are going to distribute.
    lib_teeth_names = odcutils.obj_list_from_lib(
        tooth_library)  #TODO: check if tooth_library is valid?
    tooth_objects = [[None]] * len(
        curve_teeth
    )  #we want this list to be mapped to curve_teeth with it's index...dictionary if we have to
    delete_later = []
    for i, planned_tooth in enumerate(curve_teeth):
        #this will be a one item list
        tooth_in_scene = [
            tooth for tooth in context.scene.odc_teeth
            if tooth.name.startswith(planned_tooth)
        ]
        if link and len(tooth_in_scene):
            #check if the restoration is already there...if so, use it
            if tooth_in_scene[0].contour:
                tooth_objects[i] = bpy.data.objects[tooth_in_scene[0].contour]

            #if it's not there, add it in, and associate it with ODCTooth Object
            else:
                for tooth in lib_teeth_names:
                    if tooth.startswith(
                            planned_tooth
                    ):  #necessary that the planned teeth have logical names

                        new_name = tooth + "_ArchPlanned"
                        if new_name in bpy.data.objects:
                            ob = bpy.data.objects[new_name]
                            me = ob.data
                            ob.user_clear()
                            bpy.data.objects.remove(ob)
                            bpy.data.meshes.remove(me)
                            context.scene.update()

                        odcutils.obj_from_lib(tooth_library, tooth)
                        ob = bpy.data.objects[tooth]
                        context.scene.objects.link(ob)
                        ob.name = new_name
                        tooth_objects[i] = ob
                        tooth_in_scene[0].contour = ob.name
                        break  #in case there are multiple copies?
        else:  #the tooth is not existing restoration, and we want to put it in anyway
            for tooth in lib_teeth_names:
                if tooth.startswith(planned_tooth):
                    new_name = tooth + "_ArchPlanned"
                    if new_name in bpy.data.objects:
                        ob = bpy.data.objects[new_name]
                        me = ob.data
                        ob.user_clear()
                        bpy.data.objects.remove(ob)
                        bpy.data.meshes.remove(me)
                        context.scene.update()

                    odcutils.obj_from_lib(tooth_library, tooth)
                    ob = bpy.data.objects[tooth]
                    ob.name += "_ArchPlanned"
                    if limit:
                        context.scene.objects.link(ob)
                        delete_later.append(ob)
                    else:
                        context.scene.objects.link(ob)

                    tooth_objects[i] = ob
                    break
    if debug:
        print(tooth_objects)

    #secretly, we imported the whole quadrant..we will delete them later
    teeth_len = 0
    lengths = [[0]] * len(curve_teeth)  #list of tooth mesial/distal lengths
    locs = [[0]] * len(curve_teeth)  #normalized list of locations
    for i, ob in enumerate(tooth_objects):
        lengths[i] = ob.dimensions[0]

        teeth_len += ob.dimensions[0]
        locs[i] = teeth_len - ob.dimensions[0] / 2
    scale = arch_len / teeth_len
    crowding = teeth_len - arch_len

    if debug > 1:
        print(lengths)
        print(locs)
        print(scale)
        print("there is %d mm of crowding" % round(crowding, 2))
        print("there is a %d pct archlength discrepancy" %
              round(100 - scale * 100, 2))

    #scale them to the right size
    for i, ob in enumerate(tooth_objects):

        if shift == 'FOSSA':
            delta = .05
        else:
            delta = 0
        #resize it
        ob.scale[0] *= scale + delta
        ob.scale[1] *= scale + delta
        ob.scale[2] *= scale + delta

        #find the location of interest we want?
        # bbox center, cusp tip? fossa/grove, incisal edge?
        #TODO:  odcutils.tooth_features(tooth,feature)  (world coords or local?)
        ob.location = Vector((0, 0, 0))
        if ob.rotation_mode != 'QUATERNION':
            ob.rotation_mode = 'QUATERNION'

        ob.rotation_quaternion = Quaternion((1, 0, 0, 0))

        #center line...we want palatinal face median point z,y with midpointx and center line min local z
        #buccal line...we want incisal edge median local y, maxlocal z, midpoing bbox x and buccal cusp max z?

        context.scene.objects.active = ob
        ob.select = True
        ob.hide = False

        ob.constraints.new('FOLLOW_PATH')
        path_constraint = ob.constraints["Follow Path"]
        path_constraint.target = arch
        path_constraint.use_curve_follow = True
        #find out if we cross the midline
        if sextant in ['MAX', 'MAND', 'UA', 'LA']:
            path_constraint.forward_axis = 'FORWARD_X'
            if int(curve_teeth[i]) > 20 and int(curve_teeth[i]) < 40:
                path_constraint.forward_axis = 'TRACK_NEGATIVE_X'
        else:
            path_constraint.forward_axis = 'FORWARD_X'

        path_constraint.offset = 100 * (-1 + locs[i] / teeth_len)

    #after arranging them on the curve, make a 2nd pass to spin them or not

    #decrease in number means mesial.  Except at midline.this will happen
    #we have constructed curve_teeth such that there will never be a non
    #integer change in adjacent list members. #eg,
    #quaternion rotation rules
    # Qtotal = Qa * Qb represtnts rotation b followed by rotation a
    #what we are doing is testing the occlusal direction of one tooth vs the arch occlusal direction
    context.scene.update()
    ob_dist = tooth_objects[1]
    ob_mes = tooth_objects[0]
    mesial = int(curve_teeth[1]) - int(
        curve_teeth[0]) == 1  #if true....distal numbers > mesial numbers
    vect = ob_mes.matrix_world * ob_mes.location - ob_dist.matrix_world * ob_dist.location
    spin = (vect.dot(ob_dist.matrix_world.to_quaternion() * Vector(
        (1, 0, 0))) < 0) == mesial

    tooth_occ = ob_mes.matrix_world.to_quaternion() * Vector((0, 0, 1))
    flip = tooth_occ.dot(occ_dir) > 0

    if debug:
        print('We will flip the teeth: %s. We will spin the teeth: %s.' %
              (str(flip), str(spin)))

    for ob in tooth_objects:
        if flip:
            ob.rotation_quaternion = Quaternion((0, 1, 0, 0))
        if spin:
            ob.rotation_quaternion = Quaternion(
                (0, 0, 0, 1)) * ob.rotation_quaternion

    for i, ob in enumerate(tooth_objects):

        if shift == 'BUCCAL':
            groups = [
                "Incisal Edge", "Distobuccal Cusp", "Mesiobuccal Cusp",
                "Buccal Cusp"
            ]
            inds = []
            for vgroup in groups:
                if vgroup in ob.vertex_groups:
                    inds += odcutils.vert_group_inds_get(context, ob, vgroup)
            max_z = 0
            max_ind = 0
            for j in inds:
                z = ob.data.vertices[j].co[2]
                if z > max_z:
                    max_ind = j
                    max_z = z
            tip = ob.data.vertices[max_ind].co
            tooth_shift = Vector(
                (0, tip[1] * ob.scale[1], tip[2] * ob.scale[2]))

            if sextant in [
                    'MAX', 'MAND', 'UA', 'LA'
            ]:  #no freakin idea why this is happening, but empirically, it's working
                tooth_shift[1] *= -1

            ob.location += (-1 + 2 * flip) * tooth_shift

        if shift == 'FOSSA':
            groups = ["Middle Fissure", "Palatinal Face"]
            inds = []
            for vgroup in groups:
                if vgroup in ob.vertex_groups and vgroup == "Middle Fissure":
                    inds += odcutils.vert_group_inds_get(context, ob, vgroup)

                    min_z = ob.dimensions[2]
                    min_ind = 0
                    for j in inds:
                        z = ob.data.vertices[j].co[2]
                        if z < min_z:
                            min_ind = j
                            min_z = z
                            depth = ob.data.vertices[min_ind].co
                            tooth_shift = Vector((0, depth[1] * ob.scale[1],
                                                  depth[2] * ob.scale[2]))

                elif vgroup in ob.vertex_groups and vgroup == "Palatinal Face":
                    inds += odcutils.vert_group_inds_get(context, ob, vgroup)
                    mx = Matrix.Identity(4)
                    com = odcutils.get_com(ob.data, inds, mx)
                    tooth_shift = odcutils.scale_vec_mult(
                        com, ob.matrix_world.to_scale())

            if sextant in [
                    'MAX', 'MAND', 'UA', 'LA'
            ]:  #no freakin idea why this is happening, but empirically, it's working
                tooth_shift[1] *= -1

            ob.location += (-1 + 2 * flip) * tooth_shift

    if limit:
        bpy.ops.object.select_all(action='DESELECT')
        for ob in delete_later:
            ob.select = True

        context.scene.objects.active = ob
        bpy.ops.object.delete()