class OPENDENTAL_OT_add_bone_roots(bpy.types.Operator): """Set the axis and direction of the roots for crowns from view""" bl_idname = "opendental.add_bone_roots" bl_label = "Add bone roots" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): if context.mode != 'OBJECT': return False else: return True def set_axis(self, context, event): if not self.target: return empty_name = self.target.name + 'root_empty' if empty_name in context.scene.objects: ob = context.scene.objects[empty_name] ob.empty_draw_type = 'SINGLE_ARROW' ob.empty_draw_size = 10 else: ob = bpy.data.objects.new(empty_name, None) ob.empty_draw_type = 'SINGLE_ARROW' ob.empty_draw_size = 10 context.scene.objects.link(ob) coord = (event.mouse_region_x, event.mouse_region_y) v3d = context.space_data rv3d = v3d.region_3d view_vector = view3d_utils.region_2d_to_vector_3d( context.region, rv3d, coord) ray_origin = view3d_utils.region_2d_to_origin_3d( context.region, rv3d, coord) ray_target = ray_origin + (view_vector * 1000) if bversion() < '002.077.000': res, obj, loc, no, mx = context.scene.ray_cast( ray_origin, ray_target) else: res, loc, no, ind, obj, mx = context.scene.ray_cast( ray_origin, view_vector) if res: if obj != self.target: return ob.location = loc else: return if ob.rotation_mode != 'QUATERNION': ob.rotation_mode = 'QUATERNION' vrot = rv3d.view_rotation ob.rotation_quaternion = vrot def advance_next_prep(self, context): if self.target == None: self.target = self.units[0] ind = self.units.index(self.target) prev = int(math.fmod(ind + 1, len(self.units))) self.target = self.units[prev] self.message = "Set axis for %s" % self.target.name self.target_box.raw_text = self.message self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() for obj in context.scene.objects: obj.select = False self.target.select = True context.space_data.region_3d.view_location = self.target.location def select_prev_unit(self, context): if self.target == None: self.target = self.units[0] ind = self.units.index(self.target) prev = int(math.fmod(ind - 1, len(self.units))) self.target = self.units[prev] self.message = "Set axis for %s" % self.target.name self.target_box.raw_text = self.message self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() for obj in context.scene.objects: obj.select = False self.target.select = True context.space_data.region_3d.view_location = self.target.location def update_selection(self, context): if not len(context.selected_objects): self.message = "Right Click to Select" self.target = None return if context.selected_objects[0] not in self.units: self.message = "Selected Object must be tooth" self.target = None return self.target = context.selected_objects[0] self.message = "Set axis for %s" % self.target.name self.target_box.raw_text = self.message self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() def empties_to_bones(self, context): bpy.ops.object.select_all(action='DESELECT') arm_ob = bpy.data.objects['Roots'] arm_ob.select = True context.scene.objects.active = arm_ob bpy.ops.object.mode_set(mode='EDIT') for ob in self.units: e = context.scene.objects.get(ob.name + 'root_empty') b = arm_ob.data.edit_bones.get(ob.name + 'root') if e != None and b != None: b.transform( e.matrix_world) #this gets the local x,y,z in order Z = e.matrix_world.to_quaternion() * Vector((0, 0, 1)) b.tail.xyz = e.location b.head.xyz = e.location - 16 * Z b.head_radius = 1.5 b.tail_radius = 2.5 context.scene.objects.unlink(e) e.user_clear() bpy.data.objects.remove(e) else: print('missing bone or empty') bpy.ops.object.mode_set(mode='OBJECT') def modal_main(self, context, event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' if event.type in {'RIGHTMOUSE'} and event.value == 'PRESS': self.update_selection(context) return 'pass' elif event.type == 'RIGHTMOUSE' and event.value == 'RELEASE': self.update_selection(context) if len(context.selected_objects): context.space_data.region_3d.view_location = context.selected_objects[ 0].location return 'main' elif event.type in {'LEFTMOUSE'} and event.value == 'PRESS': self.set_axis(context, event) self.advance_next_prep(context) return 'main' elif event.type in {'DOWN_ARROW'} and event.value == 'PRESS': self.select_prev_unit(context) return 'main' elif event.type in {'UP_ARROW'} and event.value == 'PRESS': self.advance_next_prep(context) return 'main' elif event.type in {'ESC'}: #keep track of and delete new objects? reset old transforms? return 'cancel' elif event.type in {'RET'} and event.value == 'PRESS': self.empties_to_bones(context) return 'finish' return 'main' def modal_nav(self, event): events_nav = { 'MIDDLEMOUSE', 'WHEELINMOUSE', 'WHEELOUTMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE' } #TODO, better navigation, another tutorial handle_nav = False handle_nav |= event.type in events_nav if handle_nav: return 'nav' return '' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['pass'] = self.modal_main FSM['nav'] = self.modal_nav nmode = FSM[self.mode](context, event) if nmode == 'nav': return {'PASS_THROUGH'} if nmode in {'finish', 'cancel'}: #clean up callbacks bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode == 'pass': self.mode = 'main' return {'PASS_THROUGH'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self, context, event): settings = get_settings() dbg = settings.debug if context.space_data.region_3d.is_perspective: #context.space_data.region_3d.is_perspective = False bpy.ops.view3d.view_persportho() if context.space_data.type != 'VIEW_3D': self.report({'WARNING'}, "Active space must be a View3d") return {'CANCELLED'} #gather all the teeth in the scene TODO, keep better track self.units = [] for i in TOOTH_NUMBERS: ob = context.scene.objects.get(str(i)) if ob != None and not ob.hide: self.units.append(ob) if not len(self.units): self.report({ 'ERROR' }, "There are no teeth in the scene!, Teeth must be named 2 digits eg 11 or 46" ) return {'CANCELLED'} self.target = self.units[0] self.message = "Set axis for %s" % self.target.name #check for an armature bpy.ops.object.select_all(action='DESELECT') if context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') if context.scene.objects.get('Roots'): root_arm = context.scene.objects.get('Roots') root_arm.select = True root_arm.hide = False context.scene.objects.active = root_arm bpy.ops.object.mode_set(mode='EDIT') for ob in self.units: if ob.name + 'root' not in root_arm.data.bones: bpy.ops.armature.bone_primitive_add(name=ob.name + 'root') else: root_data = bpy.data.armatures.new('Roots') root_arm = bpy.data.objects.new('Roots', root_data) context.scene.objects.link(root_arm) root_arm.select = True context.scene.objects.active = root_arm bpy.ops.object.mode_set(mode='EDIT') for ob in self.units: bpy.ops.armature.bone_primitive_add(name=ob.name + 'root') bpy.ops.object.mode_set(mode='OBJECT') root_arm.select = False self.units[0].select = True help_txt = "Right click to select a tooth \n Align View with root, mes and distal\n Up Arrow and Dn Arrow to select different units \n Left click in middle of prep to set axis \n Enter to finish \n ESC to cancel" self.help_box = TextBox(context, 500, 500, 300, 200, 10, 20, help_txt) self.help_box.fit_box_width_to_text_lines() self.help_box.fit_box_height_to_text_lines() self.help_box.snap_to_corner(context, corner=[1, 1]) aspect, mid = menu_utils.view3d_get_size_and_mid(context) self.target_box = TextBox(context, mid[0], aspect[1] - 20, 300, 200, 10, 20, self.message) self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() self.target_box.fit_box_height_to_text_lines() self.mode = 'main' context.window_manager.modal_handler_add(self) self._handle = bpy.types.SpaceView3D.draw_handler_add( insertion_axis_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') return {'RUNNING_MODAL'}
class OPENDENTAL_OT_fast_label_teeth(bpy.types.Operator): """Label teeth by clicking on them""" bl_idname = "opendental.fast_label_teeth" bl_label = "Fast Label Teeth" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): if context.mode != 'OBJECT': return False else: return True def set_axis(self, context, event): coord = (event.mouse_region_x, event.mouse_region_y) v3d = context.space_data rv3d = v3d.region_3d view_vector = view3d_utils.region_2d_to_vector_3d( context.region, rv3d, coord) ray_origin = view3d_utils.region_2d_to_origin_3d( context.region, rv3d, coord) ray_target = ray_origin + (view_vector * 1000) if bversion() < '002.077.000': res, obj, loc, no, mx = context.scene.ray_cast( ray_origin, ray_target) else: res, loc, no, ind, obj, mx = context.scene.ray_cast( ray_origin, view_vector) if res: obj.name = str(self.target) for ob in bpy.data.objects: ob.select = False obj.select = True obj.show_name = True context.scene.objects.active = obj bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') return True else: return False def advance_next_prep(self, context): def next_ind(n): if math.fmod(n, 10) < 7: return n + 1 elif math.fmod(n, 10) == 7: if n == 17: return 21 elif n == 27: return 31 elif n == 37: return 41 elif n == 47: return 11 self.target = next_ind(self.target) self.message = "Click on tooth % i" % self.target self.target_box.raw_text = self.message self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() def select_prev_unit(self, context): def prev_ind(n): if math.fmod(n, 10) > 1: return n - 1 elif math.fmod(n, 10) == 1: if n == 11: return 41 elif n == 21: return 11 elif n == 31: return 21 elif n == 41: return 31 self.target = prev_ind(self.target) self.message = "Click on tooth %i" % self.target self.target_box.raw_text = self.message self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() def modal_main(self, context, event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' if event.type in {'RIGHTMOUSE'} and event.value == 'PRESS': self.advance_next_prep(context) return 'pass' elif event.type in {'LEFTMOUSE'} and event.value == 'PRESS': res = self.set_axis(context, event) if res: self.advance_next_prep(context) return 'main' elif event.type in {'DOWN_ARROW'} and event.value == 'PRESS': self.select_prev_unit(context) return 'main' elif event.type in {'UP_ARROW'} and event.value == 'PRESS': self.advance_next_prep(context) return 'main' elif event.type in {'ESC'}: #keep track of and delete new objects? reset old transforms? return 'cancel' elif event.type in {'RET'} and event.value == 'PRESS': #self.empties_to_bones(context) return 'finish' return 'main' def modal_nav(self, event): events_nav = { 'MIDDLEMOUSE', 'WHEELINMOUSE', 'WHEELOUTMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE' } #TODO, better navigation, another tutorial handle_nav = False handle_nav |= event.type in events_nav if handle_nav: return 'nav' return '' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['pass'] = self.modal_main FSM['nav'] = self.modal_nav nmode = FSM[self.mode](context, event) if nmode == 'nav': return {'PASS_THROUGH'} if nmode in {'finish', 'cancel'}: #clean up callbacks bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode == 'pass': self.mode = 'main' return {'PASS_THROUGH'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self, context, event): settings = get_settings() dbg = settings.debug if context.space_data.region_3d.is_perspective: #context.space_data.region_3d.is_perspective = False bpy.ops.view3d.view_persportho() if context.space_data.type != 'VIEW_3D': self.report({'WARNING'}, "Active space must be a View3d") return {'CANCELLED'} #gather all the teeth in the scene TODO, keep better track self.target = 11 self.message = "Set axis for " + str(self.target) if context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') #check for an armature bpy.ops.object.select_all(action='DESELECT') help_txt = "Left click on the tooth indicated to label it. Right click skip a tooth \n Up or Dn Arrow to change label\n Enter to finish \n ESC to cancel" self.help_box = TextBox(context, 500, 500, 300, 200, 10, 20, help_txt) self.help_box.fit_box_width_to_text_lines() self.help_box.fit_box_height_to_text_lines() self.help_box.snap_to_corner(context, corner=[1, 1]) aspect, mid = menu_utils.view3d_get_size_and_mid(context) self.target_box = TextBox(context, mid[0], aspect[1] - 20, 300, 200, 10, 20, self.message) self.target_box.format_and_wrap_text() self.target_box.fit_box_width_to_text_lines() self.target_box.fit_box_height_to_text_lines() self.mode = 'main' context.window_manager.modal_handler_add(self) self._handle = bpy.types.SpaceView3D.draw_handler_add( rapid_label_teeth_callback, (self, context), 'WINDOW', 'POST_PIXEL') return {'RUNNING_MODAL'}