def poll(self, context): #picks tooth based on selected/active object...will refactor soon if len(odcutils.tooth_selection(context)): tooth = odcutils.tooth_selection(context)[0] if tooth.prep_model and tooth.prep_model in bpy.data.objects: return True else: return False
def poll(cls, context): #restoration exists and is in scene if not hasattr(context.scene, 'odc_props'): return False if not len(context.scene.odc_teeth): return False if not len(odcutils.tooth_selection(context)): return False tooth = odcutils.tooth_selection(context)[ 0] #TODO:...make this poll work for all selected teeth... condition_1 = tooth.margin in bpy.data.objects return condition_1
def execute(self, context): #picks tooth based on selected/active object...will refactor soon tooth = odcutils.tooth_selection(context) sce = bpy.context.scene a = tooth.name prep = tooth.prep_model Prep = bpy.data.objects[prep] #TODO scene preservation #add tracer Tracer = odcutils.tracer_mesh_add(self.radius, context.scene.cursor_location, self.spokes, .1, Prep) Tracer.name += "_tracer" #TODO add margin tracer to property group odcutils.align_to_view(context, Tracer) #TODO project into prep surface #TODO Align to view return {'FINISHED'}
def crown_help_parser(scene): if not hasattr(scene, 'odc_props'): print('no ODC') return selections = odcutils.tooth_selection(bpy.context) #weird, how do I specify better arguments? sel_names = [item.name for item in selections] help_text = 'Crown Help Wizard \n\n' if len(selections) == 0: help_text += 'No teeth in project! "Plan Multiple" to get started' update_help_box(help_text) return if not (bpy.context.scene.odc_props.master and bpy.context.scene.odc_props.master in bpy.data.objects): help_text += 'Select and Set a Master Model!' update_help_box(help_text) return help_text += 'Selected Unit: ' + ', '.join(sel_names) + '\n' for tooth in selections: help_text += tooth_help_text(tooth) update_help_box(help_text)
def execute(self, context): #add a textbox to display information. attach it to this #add a persisetent callback on scene update #which monitors the status of the ODC #clear previous handlers clear_help_handlers() global crown_help_app_handle crown_help_app_handle = bpy.app.handlers.scene_update_pre.append(crown_help_parser) global help_display_box if help_display_box != None: del help_display_box help_text = 'Open Dental Crown Help Wizard \n' selections = odcutils.tooth_selection(bpy.context) #weird, how do I specify better arguments? sel_names = [item.name for item in selections] help_text += 'Selected Units: ' + ', '.join(sel_names) + '\n' help_text += 'Next Step: ' + 'TBA' help_display_box = TextBox(context,500,500,300,100,10,20, help_text) help_display_box.snap_to_corner(context, corner = [0,1]) global crown_help_draw_handle crown_help_draw_handle = bpy.types.SpaceView3D.draw_handler_add(odc_help_draw, (self, context), 'WINDOW', 'POST_PIXEL') return {'FINISHED'}
def poll(cls, context): #restoration exists and is in scene teeth = odcutils.tooth_selection(context) if teeth != []: #This can only happen one tooth at a time tooth = teeth[0] return tooth.prep_model in bpy.data.objects else: return False
def poll(cls, context): #restoration exists and is in scene teeth = odcutils.tooth_selection( context) #TODO:...make this poll work for all selected teeth... condition = False if teeth and len(teeth) > 0: condition = True return condition
def execute(self, context): sce = bpy.context.scene tooth = odcutils.tooth_selection(context)[ 0] #Can only happen on one tooth at a time a = tooth.name prep = tooth.prep_model margin = str(a + "_Margin") if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') Prep = bpy.data.objects[prep] Prep.hide = False Prep.show_transparent = False bpy.ops.object.select_all(action='DESELECT') Prep.select = True sce.objects.active = Prep 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 sce.cursor_location = Prep_Center ###Keep a list of unhidden objects? for o in sce.objects: if o.name != prep and not o.hide: o.hide = True bpy.ops.view3d.viewnumpad(type='FRONT') bpy.ops.view3d.view_orbit(type='ORBITDOWN') current_grease = [gp.name for gp in bpy.data.grease_pencil] bpy.ops.gpencil.data_add() bpy.ops.gpencil.layer_add() for gp in bpy.data.grease_pencil: if gp.name not in current_grease: print(gp.name) gplayer = gp.layers[0] gp.draw_mode = 'SURFACE' gp.name = margin + '_tracer' gplayer.info = margin + '_tracer' return {'FINISHED'}
def bridge_from_selection(context, debug=False): #TODO check univ vs intl system teeth = odcutils.tooth_selection(context) names = [tooth.name for tooth in teeth] #check axes univ_names = [odcutils.intntl_universal[int(name)] for name in names] univ_names.sort() bridge_start = int(min(univ_names)) bridge_end = int(max(univ_names)) bridge_name = str(odcutils.universal_intntl[bridge_start]) + "x" + str( odcutils.universal_intntl[bridge_end]) if debug: print(bridge_name) n = len(context.scene.odc_bridges) context.scene.odc_bridges.add() bridge = context.scene.odc_bridges[n] tooth_list = [tooth.name for tooth in teeth] bridge.tooth_string = ":".join(tooth_list) bridge.name = bridge_name
def execute(self, context): #picks tooth based on selected/active object...will refactor soon tooth = odcutils.tooth_selection(context)[0] sce = bpy.context.scene a = tooth.name mesial = tooth.mesial distal = tooth.distal margin = tooth.margin axis = tooth.axis layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True if margin not in bpy.data.objects: self.report({'ERROR'}, 'No Margin to accept!') return {'CANCELLED'} if axis not in bpy.data.objects: self.report({'ERROR'}, 'No insertion axis for ' + a + ', please define insertion axis') print(tooth.margin) print(tooth.axis) print(tooth.name) print([ob.name for ob in bpy.data.objects]) return {'CANCELLED'} Margin = bpy.data.objects[margin] Axis = bpy.data.objects[axis] if Margin.type != 'MESH': me_data = odcutils.bezier_to_mesh(Margin, tooth.margin, n_points=200) mx = Margin.matrix_world context.scene.objects.unlink(Margin) bpy.data.objects.remove(Margin) new_obj = bpy.data.objects.new(tooth.margin, me_data) new_obj.matrix_world = mx context.scene.objects.link(new_obj) tooth.margin = new_obj.name #just in case of name collisions Margin = new_obj master = sce.odc_props.master Margin.dupli_type = 'NONE' if mesial: bpy.data.objects[mesial].hide = False if distal: bpy.data.objects[distal].hide = False if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') psuedo_margin = str(a + "_Psuedo Margin") tooth.pmargin = psuedo_margin p_margin_me = Margin.to_mesh(context.scene, True, 'PREVIEW') p_margin_bme = bmesh.new() p_margin_bme.from_mesh(p_margin_me) Z = Axis.matrix_world.to_3x3() * Vector((0, 0, 1)) odcutils.extrude_bmesh_loop(p_margin_bme, p_margin_bme.edges, Margin.matrix_world, Z, .2, move_only=True) odcutils.extrude_bmesh_loop(p_margin_bme, p_margin_bme.edges, Margin.matrix_world, Z, -.4, move_only=False) p_margin_bme.to_mesh(p_margin_me) PMargin = bpy.data.objects.new(psuedo_margin, p_margin_me) PMargin.matrix_world = Margin.matrix_world context.scene.objects.link(PMargin) bpy.data.objects[psuedo_margin].hide = True bpy.context.tool_settings.use_snap = False bpy.context.tool_settings.proportional_edit = 'DISABLED' #Now we want to overpack the verts so that when the edge of the #restoration is snapped to it, it won't displace them too much # I have estimated ~25 microns as a fine linear packin #density....another option is to leave the curve as an #implicit function.... hmmmmm for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[4] = True return {'FINISHED'}
def execute(self, context): tooth = odcutils.tooth_selection(context)[0] sce = bpy.context.scene layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True prep = tooth.prep_model Prep = bpy.data.objects.get(prep) margin = tooth.margin if margin not in bpy.data.objects: self.report({'ERROR'}, 'No Margin to Refine! Please mark margin first') if not Prep: self.report({'WARNING'}, 'No Prep to snap margin to!') Margin = bpy.data.objects[margin] Margin.dupli_type = 'NONE' if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') bpy.context.tool_settings.use_snap = True bpy.context.tool_settings.snap_target = 'ACTIVE' bpy.context.tool_settings.snap_element = 'FACE' if Margin.type != 'MESH': me_data = odcutils.bezier_to_mesh(Margin, tooth.margin, n_points=200) mx = Margin.matrix_world context.scene.objects.unlink(Margin) bpy.data.objects.remove(Margin) new_obj = bpy.data.objects.new(tooth.margin, me_data) new_obj.matrix_world = mx context.scene.objects.link(new_obj) tooth.margin = new_obj.name #just in case of name collisions Margin = new_obj Margin.select = True sce.objects.active = Margin bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') if Prep and 'SHRINKWRAP' not in Margin.modifiers: i = len(Margin.modifiers) bpy.ops.object.modifier_add(type='SHRINKWRAP') mod = Margin.modifiers[i] mod.target = Prep mod.show_on_cage = True bpy.context.tool_settings.use_snap = True bpy.context.tool_settings.snap_target = 'ACTIVE' bpy.context.tool_settings.snap_element = 'FACE' bpy.context.tool_settings.proportional_edit = 'ENABLED' bpy.context.tool_settings.proportional_size = 1 bpy.ops.object.editmode_toggle() for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[4] = True return {'FINISHED'}
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 execute(self, context): layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True tooth = odcutils.tooth_selection(context)[0] sce = bpy.context.scene a = tooth.name prep = tooth.prep_model margin = str(a + "_Margin") Prep = bpy.data.objects[prep] Prep.hide = False L = Prep.location #L = bpy.context.scene.cursor_location ###Keep a list of unhidden objects for o in sce.objects: if o.name != prep and not o.hide: o.hide = True master = sce.odc_props.master if master: Master = bpy.data.objects[master] else: self.report('WARNING', "No master model...there are risks!") bpy.ops.view3d.viewnumpad(type='TOP') bpy.ops.object.select_all(action='DESELECT') #bpy.context.scene.cursor_location = L bpy.ops.curve.primitive_bezier_curve_add(view_align=True, enter_editmode=True, location=L) bpy.context.tool_settings.use_snap = True bpy.context.tool_settings.snap_target = 'ACTIVE' bpy.context.tool_settings.snap_element = 'FACE' bpy.context.tool_settings.proportional_edit = 'DISABLED' o = bpy.context.object o.name = margin if master: o.parent = Master #maybe this should go in the "Accept Margin" function/step bpy.ops.curve.handle_type_set(type='AUTOMATIC') bpy.ops.curve.select_all(action='DESELECT') bpy.context.object.data.splines[0].bezier_points[ 1].select_control_point = True bpy.ops.curve.delete() bpy.ops.curve.select_all(action='SELECT') bpy.ops.object.modifier_add(type='SHRINKWRAP') mod = bpy.context.object.modifiers[0] #this could also be the active object...? #in a different behavior mode... mod.target = Prep tooth.margin = margin odcutils.layer_management(sce.odc_teeth) for i, layer in enumerate(layers_copy): context.scene.layers[i] = layer context.scene.layers[4] = True return {'FINISHED'}
def invoke(self, context, event): layers_copy = [layer for layer in context.scene.layers] context.scene.layers[0] = True tooth = odcutils.tooth_selection(context)[0] self.tooth = tooth sce = bpy.context.scene a = tooth.name prep = tooth.prep_model margin = str(a + "_Margin") self.crv = None self.margin_manager = None if margin in bpy.data.objects: self.report({ 'WARNING' }, "you have already made a margin for this tooth, hit esc and then undo if you didn't want to replace it" ) if prep and prep in bpy.data.objects: Prep = bpy.data.objects[prep] Prep.hide = False L = Prep.location ###Keep a list of unhidden objects for o in sce.objects: if o.name != prep and not o.hide: o.hide = True self.crv = CurveDataManager(context, snap_type='OBJECT', snap_object=Prep, shrink_mod=True, name=margin) self.margin_manager = MarginSlicer(tooth, context, self.crv) else: self.report({ 'WARNING' }, "There is no prep for this tooth, your margin will snap to the master model or all objects in scene" ) master = sce.odc_props.master if master and master in bpy.data.objects: Master = bpy.data.objects[master] if prep not in bpy.data.objects: self.crv = CurveDataManager(context, snap_type='OBJECT', snap_object=Master, shrink_mod=True, name=margin) self.margin_manager = MarginSlicer(tooth, context, self.crv) else: self.report({'WARNING'}, "No master model...there are risks!") if not self.crv: self.crv = CurveDataManager(context, snap_type='SCENE', snap_object=None, shrink_mod=False, name=margin) tooth.margin = self.crv.crv_obj.name help_txt = "DRAW MARGIN OUTLINE\n\nLeft Click on model to draw outline \nRight click to delete a point \nLeft Click last point to make loop \n G to grab \n S to show slice \n ENTER to confirm \n ESC to cancel" self.help_box = TextBox(context, 500, 500, 300, 200, 10, 20, help_txt) self.help_box.snap_to_corner(context, corner=[1, 1]) self.mode = 'main' self._handle = bpy.types.SpaceView3D.draw_handler_add( icrnmgn_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}
def active_spanning_restoration(context, exclude=[], debug=False): ''' TODO: test robustness and implications of this logic looks at addon preferences returns a list, ''' sce = context.scene bridges = [] if not hasattr(context.scene, 'odc_props'): return [None] if len(context.scene.odc_bridges) == 0: return [None] settings = get_settings() b = settings.behavior behave_mode = settings.behavior_modes[int(b)] if behave_mode == 'LIST': #choose just one tooth in the list if len(sce.odc_bridges): bridge = sce.odc_bridges[sce.odc_bridge_index] bridges.append(bridge) elif behave_mode == 'ACTIVE': if len(sce.odc_bridges): for bridge in context.scene.odc_bridges: prop_keys = bridge.keys() prop_vals = bridge.values() if debug > 1: print(prop_keys) print(prop_vals) ob = context.object if ob.name in prop_vals: n = prop_vals.index(ob.name) this_key = prop_keys[n] if debug: print( "found the object named %s as the property value: %s in bridge: %s" % (ob.name, this_key, bridge.name)) if this_key and (this_key not in exclude): bridges.append(bridge) tooth = odcutils.tooth_selection(context)[0] for bridge in sce.odc_bridges: if tooth.name in bridge.tooth_string.split(sep=":"): if debug: print('found tooth %s in bridge: %s' % (tooth.name, bridge.name)) bridges.append(bridge) elif behave_mode == 'ACTIVE_SELECTED': #make sure the active object has priority by checking that first. if len(sce.odc_bridges): for bridge in context.scene.odc_bridges: prop_keys = bridge.keys() prop_vals = bridge.values() if context.object: if context.object.name in prop_vals: n = prop_vals.index(context.object.name) this_key = prop_keys[n] if debug: print( "found the object named %s as the property value: %s in bridge: %s" % (ob.name, this_key, bridge.name)) bridges.append(bridge) teeth = odcutils.tooth_selection(context) if teeth: for tooth in teeth: for bridge in sce.odc_bridges: if tooth.name in bridge.tooth_string.split(sep=":"): if debug: print('found tooth %s in bridge: %s' % (tooth.name, bridge.name)) if bridge not in bridges: bridges.append(bridge) if debug > 1: print(bridges) return bridges
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.collection.all_objects] context.scene.collection.all_objects[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.collection.all_objects[i] = layer context.scene.collection.all_objects[11] = True return {'FINISHED'}