Example #1
0
    def execute(self, context):
        
        tooth = odcutils.tooth_selection( context)[0]  #This could theoretically happen to multiple teeth...but not likely
        sce=bpy.context.scene

        a = tooth.name
        prep = tooth.prep_model
        margin = str(a + "_Margin")
        
        Prep = bpy.data.objects[prep]
        Prep.hide = False
        
        master=sce.odc_props.master
        Master = bpy.data.objects[master]
        
        gp_margin = bpy.data.grease_pencil.get(margin + "_tracer")
        if not gp_margin:
            self.report("ERROR", "No grease pencil margin trace mark, please 'Initiate Auto Margin' first")
            return {'CANCELLED'}
        
        #Set up the rotation center as the 3d Cursor
        for A in bpy.context.window.screen.areas:
            if A.type == 'VIEW_3D':
                for s in A.spaces:
                    if s.type == 'VIEW_3D':
                        s.pivot_point = 'CURSOR'
        
        #set the trasnform orientation to the insertion axis so our z is well definined
        #this function returns the current transform so we can put it back later
        current_transform = odcutils.transform_management(tooth, sce, bpy.context.space_data)
        
                        
        #Get the prep BBox center for later       
        prep_cent = Vector((0,0,0))
        for v in Prep.bound_box:
            prep_cent = prep_cent + Prep.matrix_world * Vector(v)
        Prep_Center = prep_cent/8
        
        
        bpy.ops.gpencil.convert(type = 'PATH')
        bpy.ops.object.select_all(action='DESELECT')
        trace_curve = bpy.data.objects[margin + "_tracer"] #refactor to test for new object in scene
        context.scene.objects.active = trace_curve
        trace_curve.select = True
        bpy.ops.object.convert(target = 'MESH')
        
        #get our data
        trace_obj = bpy.context.object
        trace_data = trace_obj.data
        
        #place the intitial shrinkwrap modifier
        bpy.ops.object.modifier_add(type='SHRINKWRAP')
        mod = trace_obj.modifiers[0]
        mod.target=Prep
        mod.show_in_editmode = True
        mod.show_on_cage = True
        
        bpy.ops.object.modifier_copy(modifier = mod.name)
        
        
        bpy.ops.object.mode_set(mode = 'EDIT')
        bpy.ops.mesh.select_all(action = 'SELECT')
         
        #test the resolution of the stroke
        #subdivide if necessary
        linear_density = odcutils.get_linear_density(me, edges, mx, debug)
        
        #flatten and space to make my simple curvature more succesful :-)
        bpy.ops.mesh.looptools_flatten(influence=90, plane='best_fit', restriction='none')
        bpy.ops.mesh.looptools_space(influence=90, input='selected', interpolation='cubic')
        
        bpy.ops.object.mode_set(mode = 'OBJECT')
        bpy.ops.object.modifier_apply(modifier = trace_obj.modifiers[1].name)
        
        #Now we should essentially have a nice, approximately 2D and evenly spaced line
        #And we will find the sharpest point and save it.
        verts = trace_data.vertices
        v_ind = [v.index for v in trace_data.vertices]
        eds = [e for e in trace_data.edges if e.select] #why would we filter for selection here?
        ed_vecs = [(verts[e.vertices[1]].co - verts[e.vertices[0]].co) for e in eds]
        locs = []
        curves = []

        for i in range(3,len(eds)-3):
            a1 = ed_vecs[i-1].angle(ed_vecs[i+1])
            a2 = ed_vecs[i-2].angle(ed_vecs[i+2])
            a3 = ed_vecs[i-3].angle(ed_vecs[i+3])
    
            l1 = ed_vecs[i-1].length + ed_vecs[i+1].length
            l2 = ed_vecs[i-2].length + ed_vecs[i+2].length
            l3 = ed_vecs[i-3].length + ed_vecs[i+3].length
    
    
            curve = 1/6 * (3*a1/l1 + 2 * a2/l2 + a3/l3)
            curves.append(curve)
    
        c = max(curves)
        n = curves.index(c)
        max_ed = eds[n+3] #need to check this indexing
        loc = .5 * (verts[max_ed.vertices[0]].co + verts[max_ed.vertices[1]].co)
        
        
        locs.append(loc)

        
        
        bpy.ops.object.mode_set(mode = 'EDIT')
        
        bpy.context.scene.cursor_location = locs[0]
        bpy.ops.transform.resize(value = (0,0,1))
        bpy.ops.mesh.looptools_space(influence=100, input='selected', interpolation='cubic')
        
        bpy.context.scene.cursor_location = Prep_Center
        bpy.ops.transform.rotate(value = (2*math.pi/self.resolution), axis = (0,0,1))
        
        bpy.ops.object.mode_set(mode = 'OBJECT')
        bpy.ops.object.modifier_copy(modifier = mod.name)
        bpy.ops.object.modifier_apply(modifier = trace_obj.modifiers[1].name)
        bpy.ops.object.mode_set(mode = 'EDIT')
        
        bpy.ops.mesh.looptools_flatten(influence=90, plane='best_fit', restriction='none')
        bpy.ops.mesh.looptools_space(influence=90, input='selected', interpolation='cubic')
        bpy.ops.object.mode_set(mode = 'OBJECT')
        
        for b in range(1,self.resolution+self.extra):
            verts = trace_data.vertices
            eds = [e for e in trace_data.edges if e.select]
            ed_vecs = [(verts[e.vertices[1]].co - verts[e.vertices[0]].co) for e in eds]
            curves = []

            for i in range(3,len(eds)-3):
                a1 = ed_vecs[i-1].angle(ed_vecs[i+1])
                a2 = ed_vecs[i-2].angle(ed_vecs[i+2])
                a3 = ed_vecs[i-3].angle(ed_vecs[i+3])
    
                l1 = ed_vecs[i-1].length + ed_vecs[i+1].length
                l2 = ed_vecs[i-2].length + ed_vecs[i+2].length
                l3 = ed_vecs[i-3].length + ed_vecs[i+3].length
    
    
                curve = 1/6 * (3*a1/l1 + 2 * a2/l2 + a3/l3)
                curves.append(curve)
    
            c = max(curves)
            n = curves.index(c)
            max_ed = eds[n+3] #need to check this indexing
            loc = .5 * (verts[max_ed.vertices[0]].co + verts[max_ed.vertices[1]].co)
        
            locs.append(loc)
            
            
            
            bpy.ops.object.mode_set(mode = 'EDIT')
            
            bpy.context.scene.cursor_location = locs[b]
            zscale = self.search/trace_obj.dimensions[2]  #if the shrinkwrapping has resulted in contraction or dilation, we want to fix that.
            bpy.ops.transform.resize(value = (0,0,zscale))
            bpy.ops.mesh.looptools_space(influence=100, input='selected', interpolation='cubic')
        
            bpy.context.scene.cursor_location = Prep_Center
            
            COM = odcutils.get_com(trace_data,v_ind,'')
            delt = locs[b] - COM
            
            bpy.ops.transform.translate(value = (delt[0], delt[1], delt[2]))
            bpy.ops.transform.rotate(value = (2*math.pi/self.resolution), axis = (0,0,1))
            
            
        
            bpy.ops.object.mode_set(mode = 'OBJECT')
            bpy.ops.object.modifier_copy(modifier = mod.name)
            bpy.ops.object.modifier_apply(modifier = trace_obj.modifiers[1].name)
            bpy.ops.object.mode_set(mode = 'EDIT')
        
            bpy.ops.mesh.looptools_flatten(influence=90, plane='best_fit', restriction='none')
            bpy.ops.mesh.looptools_space(influence=90, input='selected', interpolation='cubic')
            
            bpy.ops.object.mode_set(mode = 'OBJECT')
            bpy.ops.object.modifier_copy(modifier = mod.name)
            bpy.ops.object.modifier_apply(modifier = trace_obj.modifiers[1].name)
            bpy.ops.object.mode_set(mode = 'EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.remove_doubles(threshold = .025) #this is probably the limit of any scanner accuracy anyway
            
            bpy.ops.object.mode_set(mode = 'OBJECT')
            
        
        margin_data = bpy.data.meshes.new(margin)
        
        edges = []
        for i in range(0,len(locs)-1):
            edges.append([i,i+1])
        edges.append([len(locs)-1,0])
        faces = []
        
        margin_data.from_pydata(locs,edges,faces)
        margin_data.update()
        
        Margin = bpy.data.objects.new(margin, margin_data)
        sce.objects.link(Margin)
        
        current_objects = list(bpy.data.objects)
        bpy.ops.mesh.primitive_uv_sphere_add(size = .1)
        
        for ob in sce.objects:
            if ob not in current_objects:
                ob.name = margin + "_marker"
                ob.parent = Margin
                me = ob.data
                #me.materials.append(bpy.data.materials['intaglio_material'])
        
        Margin.dupli_type = 'VERTS'
        Margin.parent = Master     
        tooth.margin = margin
        
        #put the transform orientation back
        bpy.context.space_data.transform_orientation = current_transform
       
        return {'FINISHED'}  
Example #2
0
    def execute(self, context):

        tooth = odcutils.tooth_selection(
            context
        )[0]  #This could theoretically happen to multiple teeth...but not likely
        sce = bpy.context.scene

        a = tooth.name
        prep = tooth.prep_model
        margin = str(a + "_Margin")

        Prep = bpy.data.objects[prep]
        Prep.hide = False

        master = sce.odc_props.master
        Master = bpy.data.objects[master]

        gp_margin = bpy.data.grease_pencil.get(margin + "_tracer")
        if not gp_margin:
            self.report(
                "ERROR",
                "No grease pencil margin trace mark, please 'Initiate Auto Margin' first"
            )
            return {'CANCELLED'}

        #Set up the rotation center as the 3d Cursor
        for A in bpy.context.window.screen.areas:
            if A.type == 'VIEW_3D':
                for s in A.spaces:
                    if s.type == 'VIEW_3D':
                        s.pivot_point = 'CURSOR'

        #set the trasnform orientation to the insertion axis so our z is well definined
        #this function returns the current transform so we can put it back later
        current_transform = odcutils.transform_management(
            tooth, sce, bpy.context.space_data)

        #Get the prep BBox center for later
        prep_cent = Vector((0, 0, 0))
        for v in Prep.bound_box:
            prep_cent = prep_cent + Prep.matrix_world * Vector(v)
        Prep_Center = prep_cent / 8

        bpy.ops.gpencil.convert(type='PATH')
        bpy.ops.object.select_all(action='DESELECT')
        trace_curve = bpy.data.objects[
            margin + "_tracer"]  #refactor to test for new object in scene
        context.scene.objects.active = trace_curve
        trace_curve.select = True
        bpy.ops.object.convert(target='MESH')

        #get our data
        trace_obj = bpy.context.object
        trace_data = trace_obj.data

        #place the intitial shrinkwrap modifier
        bpy.ops.object.modifier_add(type='SHRINKWRAP')
        mod = trace_obj.modifiers[0]
        mod.target = Prep
        mod.show_in_editmode = True
        mod.show_on_cage = True

        bpy.ops.object.modifier_copy(modifier=mod.name)

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')

        #test the resolution of the stroke
        #subdivide if necessary
        linear_density = odcutils.get_linear_density(me, edges, mx, debug)

        #flatten and space to make my simple curvature more succesful :-)
        bpy.ops.mesh.looptools_flatten(influence=90,
                                       plane='best_fit',
                                       restriction='none')
        bpy.ops.mesh.looptools_space(influence=90,
                                     input='selected',
                                     interpolation='cubic')

        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.modifier_apply(modifier=trace_obj.modifiers[1].name)

        #Now we should essentially have a nice, approximately 2D and evenly spaced line
        #And we will find the sharpest point and save it.
        verts = trace_data.vertices
        v_ind = [v.index for v in trace_data.vertices]
        eds = [e for e in trace_data.edges
               if e.select]  #why would we filter for selection here?
        ed_vecs = [(verts[e.vertices[1]].co - verts[e.vertices[0]].co)
                   for e in eds]
        locs = []
        curves = []

        for i in range(3, len(eds) - 3):
            a1 = ed_vecs[i - 1].angle(ed_vecs[i + 1])
            a2 = ed_vecs[i - 2].angle(ed_vecs[i + 2])
            a3 = ed_vecs[i - 3].angle(ed_vecs[i + 3])

            l1 = ed_vecs[i - 1].length + ed_vecs[i + 1].length
            l2 = ed_vecs[i - 2].length + ed_vecs[i + 2].length
            l3 = ed_vecs[i - 3].length + ed_vecs[i + 3].length

            curve = 1 / 6 * (3 * a1 / l1 + 2 * a2 / l2 + a3 / l3)
            curves.append(curve)

        c = max(curves)
        n = curves.index(c)
        max_ed = eds[n + 3]  #need to check this indexing
        loc = .5 * (verts[max_ed.vertices[0]].co +
                    verts[max_ed.vertices[1]].co)

        locs.append(loc)

        bpy.ops.object.mode_set(mode='EDIT')

        bpy.context.scene.cursor_location = locs[0]
        bpy.ops.transform.resize(value=(0, 0, 1))
        bpy.ops.mesh.looptools_space(influence=100,
                                     input='selected',
                                     interpolation='cubic')

        bpy.context.scene.cursor_location = Prep_Center
        bpy.ops.transform.rotate(value=(2 * math.pi / self.resolution),
                                 axis=(0, 0, 1))

        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.modifier_copy(modifier=mod.name)
        bpy.ops.object.modifier_apply(modifier=trace_obj.modifiers[1].name)
        bpy.ops.object.mode_set(mode='EDIT')

        bpy.ops.mesh.looptools_flatten(influence=90,
                                       plane='best_fit',
                                       restriction='none')
        bpy.ops.mesh.looptools_space(influence=90,
                                     input='selected',
                                     interpolation='cubic')
        bpy.ops.object.mode_set(mode='OBJECT')

        for b in range(1, self.resolution + self.extra):
            verts = trace_data.vertices
            eds = [e for e in trace_data.edges if e.select]
            ed_vecs = [(verts[e.vertices[1]].co - verts[e.vertices[0]].co)
                       for e in eds]
            curves = []

            for i in range(3, len(eds) - 3):
                a1 = ed_vecs[i - 1].angle(ed_vecs[i + 1])
                a2 = ed_vecs[i - 2].angle(ed_vecs[i + 2])
                a3 = ed_vecs[i - 3].angle(ed_vecs[i + 3])

                l1 = ed_vecs[i - 1].length + ed_vecs[i + 1].length
                l2 = ed_vecs[i - 2].length + ed_vecs[i + 2].length
                l3 = ed_vecs[i - 3].length + ed_vecs[i + 3].length

                curve = 1 / 6 * (3 * a1 / l1 + 2 * a2 / l2 + a3 / l3)
                curves.append(curve)

            c = max(curves)
            n = curves.index(c)
            max_ed = eds[n + 3]  #need to check this indexing
            loc = .5 * (verts[max_ed.vertices[0]].co +
                        verts[max_ed.vertices[1]].co)

            locs.append(loc)

            bpy.ops.object.mode_set(mode='EDIT')

            bpy.context.scene.cursor_location = locs[b]
            zscale = self.search / trace_obj.dimensions[
                2]  #if the shrinkwrapping has resulted in contraction or dilation, we want to fix that.
            bpy.ops.transform.resize(value=(0, 0, zscale))
            bpy.ops.mesh.looptools_space(influence=100,
                                         input='selected',
                                         interpolation='cubic')

            bpy.context.scene.cursor_location = Prep_Center

            COM = odcutils.get_com(trace_data, v_ind, '')
            delt = locs[b] - COM

            bpy.ops.transform.translate(value=(delt[0], delt[1], delt[2]))
            bpy.ops.transform.rotate(value=(2 * math.pi / self.resolution),
                                     axis=(0, 0, 1))

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.modifier_copy(modifier=mod.name)
            bpy.ops.object.modifier_apply(modifier=trace_obj.modifiers[1].name)
            bpy.ops.object.mode_set(mode='EDIT')

            bpy.ops.mesh.looptools_flatten(influence=90,
                                           plane='best_fit',
                                           restriction='none')
            bpy.ops.mesh.looptools_space(influence=90,
                                         input='selected',
                                         interpolation='cubic')

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.modifier_copy(modifier=mod.name)
            bpy.ops.object.modifier_apply(modifier=trace_obj.modifiers[1].name)
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.remove_doubles(
                threshold=.025
            )  #this is probably the limit of any scanner accuracy anyway

            bpy.ops.object.mode_set(mode='OBJECT')

        margin_data = bpy.data.meshes.new(margin)

        edges = []
        for i in range(0, len(locs) - 1):
            edges.append([i, i + 1])
        edges.append([len(locs) - 1, 0])
        faces = []

        margin_data.from_pydata(locs, edges, faces)
        margin_data.update()

        Margin = bpy.data.objects.new(margin, margin_data)
        sce.objects.link(Margin)

        current_objects = list(bpy.data.objects)
        bpy.ops.mesh.primitive_uv_sphere_add(size=.1)

        for ob in sce.objects:
            if ob not in current_objects:
                ob.name = margin + "_marker"
                ob.parent = Margin
                me = ob.data
                #me.materials.append(bpy.data.materials['intaglio_material'])

        Margin.dupli_type = 'VERTS'
        Margin.parent = Master
        tooth.margin = margin

        #put the transform orientation back
        bpy.context.space_data.transform_orientation = current_transform

        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 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()