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 guide_help_app_handle guide_help_app_handle = bpy.app.handlers.scene_update_pre.append( guide_help_parser) global help_display_box if help_display_box != None: del help_display_box help_text = 'Open Dental Guide Help Wizard \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 guide_help_draw_handle guide_help_draw_handle = bpy.types.SpaceView3D.draw_handler_add( odc_help_draw, (self, context), 'WINDOW', 'POST_PIXEL') return {'FINISHED'}
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 implant_help_app_handle implant_help_app_handle = bpy.app.handlers.scene_update_pre.append( implant_help_parser) global help_display_box if help_display_box != None: del help_display_box help_text = 'Open Dental Implant Help Wizard \n' selections = odcutils.implant_selection( bpy.context) #weird, how do I specify better arguments? sel_names = [item.name for item in selections] help_text += 'Selected Implants: ' + ', '.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 implant_help_draw_handle implant_help_draw_handle = bpy.types.SpaceView3D.draw_handler_add( odc_help_draw, (self, context), 'WINDOW', 'POST_PIXEL') return {'FINISHED'}
class OPENDENTAL_OT_place_bracket(bpy.types.Operator): """Place Bracket on surface of selected object""" bl_idname = "opendental.place_ortho_bracket" bl_label = "Ortho Bracket Place" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): if context.mode == "OBJECT" and context.object != None: return True else: return False 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_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 == 'G' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'grab' if event.type == 'T' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'torque' if event.type == 'R' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'rotate' if event.type == 'S' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'tip' if event.type == 'MOUSEMOVE': return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.bracket_manager.place_bracket(context, x, y) return 'main' if event.type == 'RET' and event.value == 'PRESS': if self.bracket_slicer: self.bracket_slicer.cache_slice_to_grease(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': del_obj = self.bracket_manager.bracket_obj context.scene.objects.unlink(del_obj) bpy.data.objects.remove(del_obj) return 'cancel' return 'main' def modal_torque(self, context, event): # no navigation in grab mode if event.type in {'LEFTMOUSE', 'RET', 'ENTER' } and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' #elif event.type == 'MOUSEMOVE': #update the b_pt location # self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) # return 'torque' elif event.type in { 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW', 'DOWN_ARROW' }: self.bracket_manager.torque_event(event.type, event.shift) self.bracket_slicer.slice() return 'torque' def modal_rotate(self, context, event): # no navigation in grab mode if event.type in {'LEFTMOUSE', 'RET', 'ENTER' } and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' #commented out, no longer want to move the mouse #elif event.type == 'MOUSEMOVE': #update the b_pt location # self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) # return 'rotate' elif event.type in { 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'LEFT_ARROW', 'RIGHT_ARROW' }: self.bracket_manager.rotate_event(event.type, event.shift) self.bracket_slicer.slice() return 'rotate' else: return 'rotate' def modal_tip(self, context, event): # no navigation in grab mode if event.type in {'LEFTMOUSE', 'RET', 'ENTER' } and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' #commented out, no longer want to move the mouse #elif event.type == 'MOUSEMOVE': #update the b_pt location # self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) # return 'rotate' elif event.type in { 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'LEFT_ARROW', 'RIGHT_ARROW' }: self.bracket_manager.spin_event(event.type, event.shift) self.bracket_slicer.slice() return 'tip' else: return 'tip' def modal_start(self, context, event): if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type == 'MOUSEMOVE': x, y = event.mouse_region_x, event.mouse_region_y self.bracket_manager.place_bracket(context, x, y, normal=True) self.bracket_slicer.slice_mouse_move(context, event.mouse_region_x, event.mouse_region_y) return 'start' elif event.type in { 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW', 'DOWN_ARROW' }: self.bracket_manager.spin_event(event.type, event.shift) self.bracket_slicer.slice() return 'start' elif event.type == "RIGTMOUSE" and event.value == 'PRESS': del_obj = self.bracket_manager.bracket_obj context.scene.objects.unlink(del_obj) bpy.data.objects.remove(del_obj) return 'cancel' else: return 'start' def modal_grab(self, context, event): # no navigation in grab mode #uses the slicer to manage the grab if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.bracket_slicer.slice_mouse_move(context, event.mouse_region_x, event.mouse_region_y) return 'grab' elif event.type in { 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW', 'DOWN_ARROW' }: self.bracket_manager.spin_event(event.type, event.shift) self.bracket_slicer.slice() return 'grab' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['start'] = self.modal_start FSM['main'] = self.modal_main FSM['rotate'] = self.modal_rotate FSM['grab'] = self.modal_grab FSM['torque'] = self.modal_torque FSM['tip'] = self.modal_tip 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: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self, context, event): settings = get_settings() libpath = settings.ortho_lib assets = obj_list_from_lib(libpath) if settings.bracket in assets: current_obs = [ob.name for ob in bpy.data.objects] obj_from_lib(settings.ortho_lib, settings.bracket) for ob in bpy.data.objects: if ob.name not in current_obs: Bracket = ob Bracket.hide = False context.scene.objects.link(Bracket) else: Bracket = None if context.object and context.object.type == 'MESH': self.bracket_manager = BracketDataManager( context, snap_type='OBJECT', snap_object=context.object, name='Bracket', bracket=Bracket) self.bracket_slicer = BracektSlicer(context, self.bracket_manager) else: self.bracket_manager = BracketDataManager(context, snap_type='SCENE', snap_object=None, name='Bracket', bracket=Bracket) self.bracket_slicer = None help_txt = "DRAW MARGIN OUTLINE\n\nLeft Click on model to place bracket.\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 = 'start' self._handle = bpy.types.SpaceView3D.draw_handler_add( bracket_placement_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}
class D3SPLINT_OT_plane_cut_mesh_rough(bpy.types.Operator): """Click and draw a line to cut mesh""" bl_idname = "d3splint.splint_plane_cut" bl_label = "Plane Cut Mesh" bl_options = {'REGISTER', 'UNDO'} cut_method = bpy.props.EnumProperty(name = 'Mode', items = (('SURFACE','SURFACE','SURFACE'), ('SOLID', 'SOLID','SOLID')), default = 'SURFACE') @classmethod def poll(cls,context): if context.object == None: return False if context.object.type != 'MESH': return False return True 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_extend(self, context, event): if event.type == 'MOUSEMOVE': x, y = event.mouse_region_x, event.mouse_region_y #get mouse position at depth of the extrusion midpoint mouse_projected = view3d_utils.region_2d_to_location_3d(context.region, context.region_data, (x,y), self.new_geom_point) imx = self.ob.matrix_world.inverted() #calculate the delta vector local_delta = imx * mouse_projected - imx * self.new_geom_point #update bmverts position for v in self.extrude_verts: v.co = self.extrude_origins[v] + local_delta #how costly is this to do live? We will find out self.bme.to_mesh(self.ob.data) self.ob.data.update() return 'extend' elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm vert positions self.new_geom = [] self.new_geom_point = Vector((0,0,0)) self.new_geom_draw_points = [] self.extrude_verts = [] self.extrude_origins = dict() self.extrude_geom_draw_points = [] #clear "extrusion candidates" return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #delete extruded verts bmesh.ops.delete(self.bme, geom = self.extrude_verts, context = 1) self.bme.verts.ensure_lookup_table() self.bme.edges.ensure_lookup_table() self.bme.faces.ensure_lookup_table() self.bme.to_mesh(self.obj.data) self.ob.data.update() self.new_geom = [] self.new_geom_point = Vector((0,0,0)) self.new_geom_draw_points = [] self.extrude_verts = [] self.extrude_origns = dict() self.extrude_geom_draw_points = [] return 'main' 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 == 'MOUSEMOVE': x, y = event.mouse_region_x, event.mouse_region_y #######Hover Operator Level Elements ### if len(self.crv.screen_pts) == 0: if len(self.new_geom): region = context.region rv3d = context.region_data extrude_screen_point = view3d_utils.location_3d_to_region_2d(region, rv3d, self.new_geom_point) if extrude_screen_point != None: R = Vector((x,y)) - extrude_screen_point if R.length < 30: self.new_geom_point_color = (1,.1,.1,1) self.new_geom_color = (1,.1,.1,1) else: self.new_geom_color = (.8, .8, .1, 1) self.new_geom_point_color = (.1, .1, .8, 1) ######Hover Line Drawer Class Elments### self.crv.hover(context, x, y) if self.cut_method == 'SOLID': self.crv.calc_box() return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #if len(self.crv.screen_pts) >= 2: return 'main' #can't add more if len(self.crv.screen_pts) == 0: x,y = event.mouse_region_x, event.mouse_region_y if len(self.new_geom): region = context.region rv3d = context.region_data extrude_screen_point = view3d_utils.location_3d_to_region_2d(region, rv3d, self.new_geom_point) if extrude_screen_point != None: R = Vector((x,y)) - extrude_screen_point if R.length < 30: eds = [ele for ele in self.new_geom if isinstance(ele, bmesh.types.BMEdge)] new_geom = bmesh.ops.extrude_edge_only(self.bme, edges = eds) new_verts = [v for v in new_geom['geom'] if isinstance(v, bmesh.types.BMVert)] self.extrude_verts = new_verts for v in self.extrude_verts: self.extrude_origins[v] = v.co.copy() return 'extend' context.window.cursor_modal_set('KNIFE') help_txt = "Left Click again to place end of cut" self.help_box.raw_text = help_txt self.help_box.format_and_wrap_text() if len(self.crv.screen_pts) == 1: help_txt = "Left Click on side of line to delete \n click on model to limit cut to line length \n click in space to to cut infinitely." self.help_box.raw_text = help_txt self.help_box.format_and_wrap_text() x, y = event.mouse_region_x, event.mouse_region_y res = self.crv.click_add_point(context, x,y) if res == None and len(self.crv.screen_pts) == 2: print('bisecting object on 3rd click ') context.window.cursor_modal_set('WAIT') res2 = self.crv.ray_cast_ob(context, x, y) if res2: filter_geom = True else: filter_geom = False if self.cut_method == 'SOLID': self.boolean_bisect_object(context, mesh_filter= True) return 'finish' else: self.bmesh_bisect_object(context, mesh_filter= filter_geom) context.window.cursor_modal_set('KNIFE') return 'main' #help_txt = "Left Click to start a cut, then move mouse" #self.help_box.raw_text = help_txt #self.help_box.format_and_wrap_text() return 'main' return 'main' if event.type == 'P' and event.value == 'PRESS': plane = bpy.data.objects.get('Plane') if plane == None: return 'main' mx = self.crv.calc_matrix(context) plane.matrix_world = mx return 'main' #if event.type == 'K' and event.value == 'PRESS': # self.bmesh_bisect_object(context) # return 'main' if event.type == 'RET' and event.value == 'PRESS': self.finish(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': #need to return 'finish' to get the undo, since our 'cancel' function does not "reset" the bmesh data return 'finish' return 'main' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['nav'] = self.modal_nav FSM['extend'] = self.modal_extend nmode = FSM[self.mode](context, event) if nmode == 'nav': return {'PASS_THROUGH'} if nmode in {'finish','cancel'}: #clean up callbacks self.bme.free() context.window.cursor_modal_restore() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context, event): Model = context.object for ob in bpy.data.objects: if ob.name == 'Plane': continue ob.select = False ob.hide = True Model.select = True Model.hide = False #bpy.ops.view3d.view_selected() self.crv = LineDrawer(context,snap_type ='OBJECT', snap_object = Model) context.space_data.show_manipulator = False context.space_data.transform_manipulators = {'TRANSLATE'} v3d = bpy.context.space_data v3d.pivot_point = 'MEDIAN_POINT' #TODO, tweak the modifier as needed #help_txt = "Interactive Plane Cutting\n\nLeft click, move the mouse, and left click again.\nThen place your mouse on the side of the line to remove. \n Click on the model to isolate the cut to the line limits.\nClick off the model to cut beyond the end-points of the line." help_txt_open = "INTERACTIVE PLANE CUTTING OPEN\n\n- LeftClick and move mouse to define a line across your model \n- The line will stick to your mouse until you Left Click again\n- LeftClick a 3rd time on the side of the line to be cut\n- If you click on the model, the cut will be limited to the edges of the line \n- If you click off of the model, the cut will extend into space\n\n\nEXTRUDING\n\n- A dot will appear next to the most recent cut, you can click and move your mouse to extrude the new cut edge.\n- LeftClick to confirm the position of the extrusion" help_txt_solid = "INTERACTIVE PLANE CUTTING SOLID\n\n- LeftClick and move mouse to define a line across your model \n- The line will stick to your mouse until you Left Click again\n- A blue preview of the region to be deleted will present itself, everything behind the blue preview will be removed from the model\n- LeftClick a 3rd time to confirm and the operator will end" if self.cut_method == 'SOLID': help_txt = help_txt_solid else: help_txt = help_txt_open 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(plane_cut_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) self.bme= bmesh.new() self.bme.from_mesh(Model.data) self.ob = Model self.cursor_updated = True self.new_geom = [] self.new_geom_point = Vector((0,0,0)) self.new_geom_draw_points = [] self.extrude_verts = [] self.extrude_origins = dict() self.extrude_geom_draw_points = [] self.new_geom_color = (.8, .8, .1, 1) self.new_geom_point_color = (.1, .1, .8, 1) return {'RUNNING_MODAL'} def bmesh_bisect_object(self, context, mesh_filter = True): if len(self.crv.screen_pts) != 2: return mx_ob = self.ob.matrix_world imx_ob = mx_ob.inverted() imx_ob_no = imx_ob.to_3x3() #assumes no scaling mx_cut = self.crv.calc_matrix(context) imx_cut = mx_cut.inverted() #cut location in oject local coords l_cut = imx_ob * mx_cut.to_translation() #need the local direction of the normal #first, get the world direction mx_no_cut = imx_cut.transposed().to_3x3() #then transform it to the local space no_cut = imx_ob_no * mx_no_cut.to_3x3() * Vector((0,0,1)) if mesh_filter: print('filtering verts') local_x = imx_ob_no * mx_no_cut.to_3x3() * Vector((1,0,0)) local_x.normalize() w0, w1, world_y = self.crv.calc_line_limits(context) L = w1 - w0 print(L.length) filter_verts = [] for v in self.bme.verts: v_prime = v.co - l_cut vx = v_prime.dot(local_x) if abs(vx) < .5 * L.length: filter_verts += [v] filter_edges = set() filter_faces = set() for v in filter_verts: filter_edges.update(v.link_edges[:]) filter_faces.update(v.link_faces[:]) cut_geom = filter_verts + list(filter_edges) + list(filter_faces) else: cut_geom = self.bme.verts[:] + self.bme.edges[:] +self.bme.faces[:] gdict = bmesh.ops.bisect_plane(self.bme, geom=cut_geom, dist=0.000001, plane_co = l_cut, plane_no=no_cut, use_snap_center=False, clear_outer=True, clear_inner=False) new_stuff = gdict['geom_cut'] new_vs = [ele for ele in new_stuff if isinstance(ele, bmesh.types.BMVert)] new_edges = [ele for ele in new_stuff if isinstance(ele, bmesh.types.BMEdge)] self.bme.verts.ensure_lookup_table() self.bme.edges.ensure_lookup_table() self.bme.faces.ensure_lookup_table() self.new_geom = new_vs + new_edges self.new_geom_point = get_com_bme(self.bme, [v.index for v in new_vs], self.ob.matrix_world) + 5 * mx_no_cut.to_3x3()*Vector((0,0,1)) self.new_geom_draw_points = [self.ob.matrix_world * v.co for v in new_vs] self.bme.to_mesh(self.ob.data) self.ob.data.update() self.crv.screen_pts = [] #reset self.crv.selected = -1 return True def boolean_bisect_object(self, context, mesh_filter = True): mx_ob = self.ob.matrix_world imx_ob = mx_ob.inverted() imx_ob_no = imx_ob.to_3x3() #assumes no scaling mx_cut = self.crv.calc_matrix(context, depth = 'BOUNDS') imx_cut = mx_cut.inverted() #need the local direction of the normal #first, get the world direction mx_no_cut = imx_cut.transposed().to_3x3() #then transform it to the local space no_cut = imx_ob_no * mx_no_cut.to_3x3() * Vector((0,0,1)) bbox = self.ob.bound_box[:] bbox_vs = [] for v in bbox: a = Vector(v) bbox_vs += [self.ob.matrix_world * a] v_max_x= max(bbox_vs, key = lambda x: x[0]) v_min_x = min(bbox_vs, key = lambda x: x[0]) v_max_y= max(bbox_vs, key = lambda x: x[1]) v_min_y = min(bbox_vs, key = lambda x: x[1]) v_max_z= max(bbox_vs, key = lambda x: x[2]) v_min_z = min(bbox_vs, key = lambda x: x[2]) diag_xyz = (((v_max_x - v_min_x)[0])**2 + ((v_max_y - v_min_y)[1])**2+((v_max_z - v_min_z)[1])**2)**.5 cut_plane = bpy.data.meshes.new('Plane Cut') cut_ob = bpy.data.objects.new('Plane Cut', cut_plane) context.scene.objects.link(cut_ob) cut_ob.draw_type = 'WIRE' if mesh_filter: print('filtering verts') local_x = imx_ob_no * mx_no_cut.to_3x3() * Vector((1,0,0)) local_x.normalize() w0, w1, world_y = self.crv.calc_line_limits(context) L = w1 - w0 print(L.length) Lz = world_y.length cube_bme = bmesh.new() bmesh.ops.create_cube(cube_bme, size = 1, matrix = Matrix.Identity(4)) bmesh.ops.subdivide_edges(cube_bme, edges = cube_bme.edges[:], cuts = 50, use_grid_fill = True) cube_bme.to_mesh(cut_plane) T = Matrix.Translation(.5 * world_y) cut_ob.matrix_world = T * mx_cut cut_ob.scale[1] = diag_xyz cut_ob.scale[0] = L.length cut_ob.scale[2] = Lz cut_ob.hide = True else: grid_bme = bmesh.new() bmesh.ops.create_grid(grid_bme, x_segments = 150, y_segments = 150, size = diag_xyz) for f in grid_bme.faces: f.normal_flip() grid_bme.to_mesh(cut_plane) mx_cut = self.crv.calc_matrix(context) cut_ob.matrix_world = mx_cut cut_ob.hide = True mod = self.ob.modifiers.new('Plane Cut', type = 'BOOLEAN') mod.operation = 'DIFFERENCE' mod.object = cut_ob self.crv.screen_pts = [] #reset self.crv.selected = -1 return None def finish(self, context): #settings = get_settings() #apply all modifiers context.window.cursor_modal_restore() tracking.trackUsage("D3Splint:PlaneCutRaw",None)
class D3SPLINT_OT_splint_manual_auto_surface(bpy.types.Operator): """Help make a nice flat plane""" bl_idname = "d3splint.splint_manual_flat_plane" bl_label = "Define Occlusal Contacts" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls,context): return True 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_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 == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.crv.click_add_point(context, x,y, label = None) return 'main' #TODO, right click to delete misfires if event.type == 'DEL' and event.value == 'PRESS': self.crv.click_delete_point() return 'main' if event.type == 'RET' and event.value == 'PRESS': self.finish(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = 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'}: #context.space_data.show_manipulator = True if nmode == 'finish': context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'} else: context.space_data.transform_manipulators = {'TRANSLATE'} #clean up callbacks bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context, event): n = context.scene.odc_splint_index self.splint = context.scene.odc_splints[n] if self.splint.jaw_type == 'MANDIBLE': model = self.splint.get_maxilla() else: model = self.splint.get_mandible() if model == '' or model not in bpy.data.objects: self.report({'ERROR'}, "Need to mark the Upper and Lower model first!") return {'CANCELLED'} Model = bpy.data.objects[model] for ob in bpy.data.objects: ob.select = False if ob != Model: ob.hide = True Model.select = True Model.hide = False context.scene.objects.active = Model if self.splint.jaw_type == 'MAXILLA': bpy.ops.view3d.viewnumpad(type = 'TOP') else: bpy.ops.view3d.viewnumpad(type = 'BOTTOM') bpy.ops.view3d.view_selected() self.crv = PointPicker(context,snap_type ='OBJECT', snap_object = Model) context.space_data.show_manipulator = False context.space_data.transform_manipulators = {'TRANSLATE'} v3d = bpy.context.space_data v3d.pivot_point = 'MEDIAN_POINT' #TODO, tweak the modifier as needed help_txt = "Designate Posterior Contacts\n LeftClick on posterior cusp tips that will have light contact in CR/MIP bite \n A smooth surface will be generated between all ponts \n This surface will then be used to slice off the posterior rim" 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(landmarks_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} def finish(self, context): #ray cast the entire grid into if 'Posterior Plane' in bpy.data.objects: Plane = bpy.data.objects['Posterior Plane'] Plane.hide = False else: me = bpy.data.meshes.new('Posterior Plane') Plane = bpy.data.objects.new('Posterior Plane', me) context.scene.objects.link(Plane) pbme = bmesh.new() pbme.verts.ensure_lookup_table() pbme.edges.ensure_lookup_table() pbme.faces.ensure_lookup_table() bmesh.ops.create_grid(pbme, x_segments = 200, y_segments = 200, size = 39.9) pbme.to_mesh(Plane.data) pt, pno = calculate_plane(self.crv.b_pts) if self.splint.jaw_type == 'MANDIBLE': Zw = Vector((0,0,-1)) Xw = Vector((1,0,0)) Yw = Vector((0,-1,1)) else: Zw = Vector((0,0,1)) Xw = Vector((1,0,0)) Yw = Vector((0,1,0)) Z = pno Z.normalize() if Zw.dot(Z) < 0: Z *= -1 Y = Z.cross(Xw) X = Y.cross(Z) R = Matrix.Identity(3) #make the columns of matrix U, V, W R[0][0], R[0][1], R[0][2] = X[0] ,Y[0], Z[0] R[1][0], R[1][1], R[1][2] = X[1], Y[1], Z[1] R[2][0] ,R[2][1], R[2][2] = X[2], Y[2], Z[2] R = R.to_4x4() T = Matrix.Translation(pt - 5 * Z) Plane.matrix_world = T * R pmx = Plane.matrix_world ipmx = pmx.inverted() bme_pln = bmesh.new() bme_pln.from_mesh(Plane.data) bme_pln.verts.ensure_lookup_table() bme_pln.edges.ensure_lookup_table() bme_pln.faces.ensure_lookup_table() bvh = BVHTree.FromBMesh(bme_pln) #we are going to raycast the user world coordinate points #into a grid, and identify all points in the grid from the local Z direction #Then we will store the local location of the user picked coordinate in a dictionary key_verts = {} for loc in self.crv.b_pts: res = bvh.ray_cast(ipmx * loc, -Z, 30) if res[0] != None: f = bme_pln.faces[res[2]] for v in f.verts: key_verts[v] = ipmx * loc v.select_set(True) continue res = bvh.ray_cast(ipmx * loc, Z, 30) if res[0] != None: f = bme_pln.faces[res[2]] for v in f.verts: key_verts[v] = ipmx * loc v.select_set(True) continue #bme_pln.to_mesh(Plane.data) #bme_pln.free() #return kdtree = KDTree(len(key_verts)) for v in key_verts.keys(): kdtree.insert(v.co, v.index) kdtree.balance() #raycast the shell if we can raycast_shell = False if 'Splint Shell' in bpy.data.objects: shell = bpy.data.objects.get('Splint Shell') bvh_shell = BVHTree.FromObject(shell, context.scene) mx_shell = shell.matrix_world imx_shell = mx_shell.inverted() Z_shell = imx_shell.to_3x3()*Z raycast_shell = True right_side = set() left_side = set() ray_casted = set() to_delete = set() for v in bme_pln.verts: if v in key_verts: v.co[2] = key_verts[v][2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue results = kdtree.find_range(v.co, .5) if len(results): N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += 1/res[2] v_new += (1/res[2]) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue results = kdtree.find_range(v.co, 6) if len(results): N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue loc, no, index, d = bvh_shell.ray_cast(imx_shell * pmx * v.co, Z_shell) if loc: ray_casted.add(v) results = kdtree.find_n(v.co, 4) N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] continue total_verts = ray_casted | left_side | right_side ant_left = max(left_side, key = lambda x: x.co[0]) ant_right = max(right_side, key = lambda x: x.co[0]) new_verts = set() dilation_verts = set() for v in total_verts: for ed in v.link_edges: v_new = ed.other_vert(v) if v_new in total_verts or v_new in new_verts: continue else: new_verts.add(v_new) print('adding %i new verts' % len(new_verts)) total_verts.update(new_verts) dilation_verts.update(new_verts) #for v in ray_casted: # if v.co[1] > 0: # if v.co[0] > ant_left.co[0]: # to_delete.add(v) # else: # if v.co[0] > ant_right.co[0]: # to_delete.add(v) #print('added %i ray_casted' % len(ray_casted)) #total_verts = ray_casted | left_side | right_side #total_verts.difference_update(to_delete) #new_verts = set() #for v in total_verts: # for ed in v.link_edges: # v_new = ed.other_vert(v) # if v_new in total_verts: continue # if v_new.co[1] > 0 and v_new.co[0] < ant_left.co[0]: # if v in to_delete: # new_verts.add(v) # if v_new.co[1] <= 0 and v_new.co[0] < ant_right.co[0]: # if v in to_delete: # new_verts.add(v) #to_delete.difference_update(new_verts) #print('adding %i new verts' % len(new_verts)) for j in range(0,3): newer_verts = set() for v in new_verts: for ed in v.link_edges: v_new = ed.other_vert(v) if v_new in total_verts or v_new in newer_verts: continue newer_verts.add(v_new) total_verts.update(newer_verts) dilation_verts.update(newer_verts) new_verts = newer_verts to_delete = set(bme_pln.verts[:]) - total_verts #filter out anteior dilation for v in dilation_verts: if v.co[1] > 0 and v.co[0] > ant_left.co[0]: to_delete.add(v) continue if v.co[1] <= 0 and v.co[0] > ant_right.co[0]: to_delete.add(v) continue results = kdtree.find_n(v.co, 4) N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] #filter out anteior dilation for v in ray_casted: if v.co[1] > 0 and v.co[0] > ant_left.co[0]: to_delete.add(v) continue if v.co[1] <= 0 and v.co[0] > ant_right.co[0]: to_delete.add(v) continue bmesh.ops.delete(bme_pln, geom = list(to_delete), context = 1) bme_pln.to_mesh(Plane.data) Plane.data.update() smod = Plane.modifiers.new('Smooth', type = 'SMOOTH') smod.iterations = 5 smod.factor = 1 self.splint.ops_string += 'Mark Posterior Cusps:'
class OPENDENTAL_OT_mark_crown_margin(bpy.types.Operator): """Mark Margin. Draw a line with the mouse to extrude bezier curves""" bl_idname = "opendental.mark_crown_margin" bl_label = "Mark Crown Margin" bl_options = {'REGISTER', 'UNDO'} @classmethod 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 return True else: return False 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_main(self,context,event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' #after navigation filter, these are relevant events in this state if event.type == 'G' and event.value == 'PRESS': if self.crv.grab_initiate(): return 'grab' else: #error, need to select a point return 'main' if event.type == 'MOUSEMOVE': self.crv.hover(context, event.mouse_region_x, event.mouse_region_y) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.crv.click_add_point(context, x,y) return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': self.crv.click_delete_point(mode = 'mouse') return 'main' if event.type == 'X' and event.value == 'PRESS': self.crv.delete_selected(mode = 'selected') return 'main' if event.type == 'S' and event.value == 'PRESS' and self.margin_manager: self.margin_manager.prepare_slice() return 'slice' if event.type == 'RET' and event.value == 'PRESS': return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': del_obj = self.crv.crv_obj context.scene.objects.unlink(del_obj) bpy.data.objects.remove(del_obj) self.tooth.margin = '' return 'cancel' return 'main' def modal_grab(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.crv.grab_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.crv.grab_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.crv.grab_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'grab' def modal_slice(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.margin_manager.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.margin_manager.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.margin_manager.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'slice' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['grab'] = self.modal_grab FSM['slice'] = self.modal_slice 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: self.mode = nmode return {'RUNNING_MODAL'} 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'}
class D3SPLINT_OT_stencil_text(bpy.types.Operator): """Click and draw a line to place text on the model""" bl_idname = "d3splint.stencil_text" bl_label = "Stencil Text" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls,context): if context.object == None: return False if context.object.type != 'MESH': return False return True 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_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 == 'MOUSEMOVE': x, y = event.mouse_region_x, event.mouse_region_y self.crv.hover(context, x, y) if len(self.crv.screen_pts) != 2: self.crv.calc_text_values() return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #if len(self.crv.screen_pts) >= 2: return 'main' #can't add more if len(self.crv.screen_pts) == 0: context.window.cursor_modal_set('CROSSHAIR') #help_txt = "Left Click again to place end of line" #self.help_box.raw_text = help_txt #self.help_box.format_and_wrap_text() #if len(self.crv.screen_pts) == 1: #help_txt = "Left Click to end the text" #self.help_box.raw_text = help_txt #self.help_box.format_and_wrap_text() x, y = event.mouse_region_x, event.mouse_region_y res = self.crv.click_add_point(context, x,y) return 'main' if event.type == 'DEL' and event.value == 'PRESS': self.crv.click_delete_point() return 'main' if event.type == 'P' and event.value == 'PRESS': self.create_and_project_text(context) return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y v3d = context.space_data rv3d = v3d.region_3d rot = rv3d.view_rotation X = rot * Vector((1,0,0)) Y = rot * Vector((0,1,0)) Z = rot * Vector((0,0,1)) loc, no = self.crv.ray_cast_pt(context, (x,y)) no_mx = self.crv.snap_ob.matrix_world.inverted().transposed().to_3x3() world_no = no_mx * no world_no_aligned = world_no - world_no.dot(X) * X world_no_aligned.normalize() angle = world_no_aligned.angle(Z) if world_no.dot(Y) > 0: angle = -1 * angle R_mx = Matrix.Rotation(angle, 3, X) R_quat = R_mx.to_quaternion() rv3d.view_rotation = R_quat * rot return 'main' if event.type == 'LEFT_ARROW' and event.value == 'PRESS': print('reset old matrix') v3d = context.space_data rv3d = v3d.region_3d rv3d.view_rotation = self.last_view_rot rv3d.view_location = self.last_view_loc rv3d.view_matrix = self.last_view_matrix rv3d.view_distance = self.last_view_distance rv3d.update() return 'main' if event.type == 'RET' and event.value == 'PRESS': if len(self.crv.screen_pts) != 2: return 'main' if not len(self.crv.projected_points): self.create_and_project_text(context) self.finalize_text(context) self.finish(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = 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 self.bme.free() context.window.cursor_modal_restore() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context, event): label_message = get_settings().d3_model_label Model = context.object for ob in bpy.data.objects: if "D3T Label" in ob.name: continue ob.select = False ob.hide = True Model.select = True Model.hide = False #bpy.ops.view3d.view_selected() self.crv = TextLineDrawer(context,snap_type ='OBJECT', snap_object = Model, msg = label_message) context.space_data.fx_settings.use_ssao = True #TODO, tweak the modifier as needed help_txt = "INTERACTIVE LABEL STENCIL\n\n- LeftClick and move mouse to define a line across your model \n- The line will stick to your mouse until you Left Click again\n- A preview of the text label will follow your line\n -press 'ENTER' to project the text onto your model and finish the operator.\n\nADVANCED USAGE\n\n-RightMouse in the middle of the label to snap your view perpendicular to the model surface, you may need to adjust the position slightly\n- You can press 'P' to project the text onto the object without leaving the operator. You can then alter your view to inspect the text projection.\n- LEFT_ARROW key to snap back to the original view, you can then modify your viewing angle and press 'P' again. When satisfied, press 'ENTER' to finish." self.help_box = TextBox(context,500,500,300,200,10,20,help_txt) self.help_box.snap_to_corner(context, corner = [1,1]) self.bme= bmesh.new() self.bme.from_mesh(Model.data) self.ob = Model self.cursor_updated = True #get new text data and object in the scene self.txt_crv = bpy.data.curves.new("D3T Label", type = 'FONT') self.txt_crv_ob = bpy.data.objects.new("D3T Label", self.txt_crv) context.scene.objects.link(self.txt_crv_ob) context.scene.update() self.txt_crv_ob.hide = True self.txt_me_data = self.txt_crv_ob.to_mesh(context.scene, apply_modifiers = True, settings = 'PREVIEW') self.txt_me_ob = bpy.data.objects.new("D3T Label Mesh", self.txt_me_data) context.scene.objects.link(self.txt_me_ob) self.txt_crv.align_x = 'LEFT' self.txt_crv.align_y = 'BOTTOM' self.txt_crv.body = label_message #TODO hook up to property context.space_data.show_manipulator = False self.mode = 'main' self._handle = bpy.types.SpaceView3D.draw_handler_add(stencil_text_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) v3d = context.space_data rv3d = v3d.region_3d self.last_view_rot = rv3d.view_rotation self.last_view_loc = rv3d.view_location self.last_view_matrix = rv3d.view_matrix.copy() self.last_view_distance = rv3d.view_distance return {'RUNNING_MODAL'} def create_and_project_text(self, context): self.crv.project_line(context, res = 20) if len(self.crv.projected_points) == 0: return v3d = context.space_data rv3d = v3d.region_3d self.last_view_rot = rv3d.view_rotation self.last_view_loc = rv3d.view_location self.last_view_matrix = rv3d.view_matrix.copy() self.last_view_distance = rv3d.view_distance txt_ob = self.txt_crv_ob txt_ob.matrix_world = Matrix.Identity(4) bbox = txt_ob.bound_box[:] bbox_vs = [] for v in bbox: bbox_vs += [Vector(v)] v_max_x= max(bbox_vs, key = lambda x: x[0]) v_min_x = min(bbox_vs, key = lambda x: x[0]) X_dim = v_max_x[0] - v_min_x[0] print("The text object has %f length" % X_dim) #really need a path and bezier class for this kind of stuff proj_path_len = 0 s_v_map = {} s_v_map[0.0] = 0 for i in range(0,19): seg = self.crv.projected_points[i + 1] - self.crv.projected_points[i] proj_path_len += seg.length s_v_map[proj_path_len] = i+1 def find_path_len_v(s_len): ''' Get the interpolated position along a polypath at a given length along the path. ''' p_len = 0 for i in range(0, 19): seg = self.crv.projected_points[i + 1] - self.crv.projected_points[i] p_len += seg.length if p_len > s_len: delta = p_len - s_len vec = seg.normalized() v = self.crv.projected_points[i] + delta * vec return v, vec return self.crv.projected_points[i+1], seg.normalized() #place the text object on the path s_factor = proj_path_len/X_dim S = Matrix.Scale(s_factor, 4) loc = self.crv.projected_points[0] T = Matrix.Translation(loc) R = self.crv.calc_matrix(context) txt_ob.matrix_world = T * R * S me = txt_ob.to_mesh(context.scene, apply_modifiers = True, settings = 'PREVIEW') bme = bmesh.new() bme.from_mesh(me) bme.verts.ensure_lookup_table() bme.edges.ensure_lookup_table() bme.faces.ensure_lookup_table() #bullshit it doesn't work #print('dissolving degeneration') #ret = bmesh.ops.dissolve_degenerate(bme, dist = .001, edges = bme.edges[:]) #print(ret) print('beauty faces') bmesh.ops.beautify_fill(bme, faces = bme.faces[:], edges = bme.edges[:]) characters = bmesh_loose_parts(bme, selected_faces = None, max_iters = 200) #parameterize each character based on it's center of mass in the x direction #eg, it's length down the curve path ts = [] path_pts = [] for fs in characters: vs = set() com = Vector((0,0,0)) for f in fs: vs.update(f.verts[:]) for v in vs: com += v.co com *= 1/len(vs) world_com = T * R * S * com point_2d = location_3d_to_region_2d(context.region, context.space_data.region_3d, world_com) loc, no = self.crv.ray_cast_pt(context, point_2d) world_projected_com = self.crv.snap_ob.matrix_world * loc #self.crv.projected_points += [world_projected_com] world_delta = world_projected_com - world_com local_delta = (R * S).inverted().to_3x3() * world_delta ts += [com[0]/X_dim] path_pt, path_tan = find_path_len_v(com[0]/X_dim * proj_path_len) local_tan = (R * S).inverted().to_3x3() * path_tan angle_dif = Vector((1,0,0)).angle(local_tan) if local_tan.cross(Vector((1,0,0))).dot(Vector((0,1,0))) < 0: angle_dif *= -1 r_prime = Matrix.Rotation(-angle_dif, 4, 'Y') print('The angle difference is %f' % angle_dif) #translate to center for v in vs: v.co -= com v.co = r_prime * v.co v.co += com + local_delta #text mesh bme.to_mesh(me) self.txt_me_ob.data = me if self.txt_me_data != None: self.txt_me_data.user_clear() bpy.data.meshes.remove(self.txt_me_data) self.txt_me_data = me self.txt_me_ob.matrix_world = T * R * S bme.free() if 'Solidify' not in self.txt_me_ob.modifiers: mod = self.txt_me_ob.modifiers.new('Solidify',type = 'SOLIDIFY') mod.offset = 0 else: mod = self.txt_me_ob.modifiers.get('Solidify') mod.thickness = .5 * 1/s_factor #TODO put as setting return True def finalize_text(self,context): context.scene.objects.unlink(self.txt_crv_ob) bpy.data.objects.remove(self.txt_crv_ob) bpy.data.curves.remove(self.txt_crv) return def finish(self, context): #settings = get_settings() context.window.cursor_modal_restore() tracking.trackUsage("D3Splint:StencilText",None)
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_mark_crown_margin(bpy.types.Operator): """Mark Margin. Draw a line with the mouse to extrude bezier curves""" bl_idname = "opendental.mark_crown_margin" bl_label = "Mark Crown Margin" bl_options = {'REGISTER', 'UNDO'} @classmethod 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 return True else: return False 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_main(self, context, event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' #after navigation filter, these are relevant events in this state if event.type == 'G' and event.value == 'PRESS': if self.crv.grab_initiate(): return 'grab' else: #error, need to select a point return 'main' if event.type == 'MOUSEMOVE': self.crv.hover(context, event.mouse_region_x, event.mouse_region_y) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.crv.click_add_point(context, x, y) return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': self.crv.click_delete_point(mode='mouse') return 'main' if event.type == 'X' and event.value == 'PRESS': self.crv.delete_selected(mode='selected') return 'main' if event.type == 'S' and event.value == 'PRESS' and self.margin_manager: self.margin_manager.prepare_slice() return 'slice' if event.type == 'RET' and event.value == 'PRESS': return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': del_obj = self.crv.crv_obj context.scene.objects.unlink(del_obj) bpy.data.objects.remove(del_obj) self.tooth.margin = '' return 'cancel' return 'main' def modal_grab(self, context, event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.crv.grab_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.crv.grab_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.crv.grab_mouse_move(context, event.mouse_region_x, event.mouse_region_y) return 'grab' def modal_slice(self, context, event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.margin_manager.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.margin_manager.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.margin_manager.slice_mouse_move(context, event.mouse_region_x, event.mouse_region_y) return 'slice' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['grab'] = self.modal_grab FSM['slice'] = self.modal_slice 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: self.mode = nmode return {'RUNNING_MODAL'} 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'}
class OPENDENTAL_OT_mesh_trim_polyline(bpy.types.Operator): """Draw a line with the mouse to cut mesh into pieces""" bl_idname = "opendental.trim_mesh" bl_label = "Polyline Trim Mesh" bl_options = {'REGISTER', 'UNDO'} def sketch_confirm(self, context, event): print('sketch confirmed') if len(self.sketch) < 5 and self.knife.ui_type == 'DENSE_POLY': print('sketch too short, cant confirm') return x, y = event.mouse_region_x, event.mouse_region_y last_hovered = self.knife.hovered[1] #guaranteed to be a point by criteria to enter sketch mode self.knife.hover(context,x,y) print('last hovered %i' % last_hovered) sketch_3d = common_utilities.ray_cast_path(context, self.knife.cut_ob,self.sketch) if self.knife.hovered[0] == None: #add the points in if last_hovered == len(self.knife.pts) - 1: self.knife.pts += sketch_3d[0::5] print('add on to the tail') else: self.knife.pts = self.knife.pts[:last_hovered] + sketch_3d[0::5] print('snipped off and added on to the tail') else: print('inserted new segment') print('now hovered %i' % self.knife.hovered[1]) new_pts = sketch_3d[0::5] if last_hovered > self.knife.hovered[1]: new_pts.reverse() self.knife.pts = self.knife.pts[:self.knife.hovered[1]] + new_pts + self.knife.pts[last_hovered:] else: self.knife.pts = self.knife.pts[:last_hovered] + new_pts + self.knife.pts[self.knife.hovered[1]:] self.knife.snap_poly_line() 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_main(self,context,event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' #after navigation filter, these are relevant events in this state if event.type == 'G' and event.value == 'PRESS': if self.knife.grab_initiate(): return 'grab' else: #need to select a point return 'main' if event.type == 'MOUSEMOVE': self.knife.hover(context, event.mouse_region_x, event.mouse_region_y) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.knife.click_add_point(context, x,y) #takes care of selection too if self.knife.ui_type == 'DENSE_POLY' and self.knife.hovered[0] == 'POINT': self.sketch = [(x,y)] return 'sketch' return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': self.knife.click_delete_point(mode = 'mouse') return 'main' if event.type == 'X' and event.value == 'PRESS': self.knife.delete_selected(mode = 'selected') return 'main' if event.type == 'C' and event.value == 'PRESS': self.knife.make_cut() return 'main' if event.type == 'D' and event.value == 'PRESS': print('confirm cut') if len(self.knife.new_cos) and len(self.knife.bad_segments) == 0 and not self.knife.split: print('actuall confirm cut') self.knife.confirm_cut_to_mesh() return 'main' if event.type == 'E' and event.value == 'PRESS': if self.knife.split and self.knife.face_seed and len(self.knife.ed_map): self.knife.split_geometry() return 'finish' if event.type == 'S' and event.value == 'PRESS': return 'inner' if event.type == 'RET' and event.value == 'PRESS': self.knife.confirm_cut_to_mesh() return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal_grab(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.knife.grab_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.knife.grab_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.knife.grab_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'grab' def modal_sketch(self,context,event): if event.type == 'MOUSEMOVE': x, y = event.mouse_region_x, event.mouse_region_y if not len(self.sketch): return 'main' (lx, ly) = self.sketch[-1] ss0,ss1 = self.stroke_smoothing ,1-self.stroke_smoothing self.sketch += [(lx*ss0+x*ss1, ly*ss0+y*ss1)] return 'sketch' elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': self.sketch_confirm(context, event) self.sketch = [] return 'main' def modal_inner(self,context,event): if event.type == 'LEFTMOUSE' and event.value == 'PRESS': print('left click modal inner') x, y = event.mouse_region_x, event.mouse_region_y if self.knife.click_seed_select(context, x,y): print('seed set') return 'main' else: return 'inner' if event.type in {'RET', 'ESC'}: return 'main' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['sketch'] = self.modal_sketch FSM['grab'] = self.modal_grab FSM['nav'] = self.modal_nav FSM['inner'] = self.modal_inner 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: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context,event): self.mode = 'main' help_txt = "DRAW CUT OUTLINE\n\nLeft Click on model to draw outline outline \nRight click to delete \nLeft Click last point to close loop\n C to preview cut n\ Adjustt red segements and re-cut \n S and then click in region to split to make cut and split mesh \n ENTER to confirm" self.stroke_smoothing = .4 self.sketch = [] self.help_box = TextBox(context,500,500,300,200,10,20,help_txt) self.help_box.snap_to_corner(context, corner = [1,1]) self.knife = PolyLineKnife(context,context.object) self._handle = bpy.types.SpaceView3D.draw_handler_add(plyknife_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}
class OPENDENTAL_OT_splint_margin(bpy.types.Operator): """Draw a line with the mouse to extrude bezier curves""" bl_idname = "opendental.initiate_splint_outline" bl_label = "Splint Outine" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls,context): condition_1 = context.object != None return condition_1 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_main(self,context,event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' #after navigation filter, these are relevant events in this state if event.type == 'G' and event.value == 'PRESS': if self.crv.grab_initiate(): return 'grab' else: #error, need to select a point return 'main' if event.type == 'MOUSEMOVE': self.crv.hover(context, event.mouse_region_x, event.mouse_region_y) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.crv.click_add_point(context, x,y) return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': self.crv.click_delete_point(mode = 'mouse') return 'main' if event.type == 'X' and event.value == 'PRESS': self.crv.delete_selected(mode = 'selected') return 'main' if event.type == 'RET' and event.value == 'PRESS': return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal_grab(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.crv.grab_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.crv.grab_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.crv.grab_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'grab' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['grab'] = self.modal_grab 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: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context, event): if len(context.scene.odc_splints) == 0 and context.object: #This is a hidden cheat, allowing quick starting of a splint my_item = context.scene.odc_splints.add() my_item.name = context.object.name + '_Splint' my_item.model = context.object.name self.report({'WARNING'}, "Assumed you wanted to start a new splint on the active object! If not, then UNDO") for ob in bpy.data.objects: ob.select = False context.object.select = True bpy.ops.view3d.view_selected() self.splint = my_item else: self.splint = odcutils.splint_selction(context)[0] self.crv = None margin = self.splint.name + '_outline' if (self.splint.model == '' or self.splint.model not in bpy.data.objects) and not context.object: self.report({'WARNING'}, "There is no model, the curve will snap to anything in the scene!") self.crv = CurveDataManager(context,snap_type ='SCENE', snap_object = None, shrink_mod = False, name = margin) elif self.splint.model != '' and self.splint.model in bpy.data.objects: Model = bpy.data.objects[self.splint.model] for ob in bpy.data.objects: ob.select = False Model.select = True Model.hide = False context.scene.objects.active = Model bpy.ops.view3d.view_selected() self.crv = CurveDataManager(context,snap_type ='OBJECT', snap_object = Model, shrink_mod = True, name = margin) self.crv.crv_obj.parent = Model if self.crv == None: self.report({'ERROR'}, "Not sure what you want, you may need to select an object or plan a splint") return {'CANCELLED'} self.splint.margin = self.crv.crv_obj.name if 'Wrap' in self.crv.crv_obj.modifiers: mod = self.crv.crv_obj.modifiers['Wrap'] mod.offset = .75 mod.use_keep_above_surface = True mod = self.crv.crv_obj.modifiers.new('Smooth','SMOOTH') mod.iterations = 10 #TODO, tweak the modifier as needed 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 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(ispltmgn_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}
class OPENDENTAL_OT_arch_curve(bpy.types.Operator): """Draw a line with the mouse to extrude bezier curves""" bl_idname = "opendental.draw_arch_curve" bl_label = "Arch Curve" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls,context): return True 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_main(self,context,event): # general navigation nmode = self.modal_nav(event) if nmode != '': return nmode #stop here and tell parent modal to 'PASS_THROUGH' #after navigation filter, these are relevant events in this state if event.type == 'G' and event.value == 'PRESS': if self.crv.grab_initiate(): return 'grab' else: #error, need to select a point return 'main' if event.type == 'MOUSEMOVE': self.crv.hover(context, event.mouse_region_x, event.mouse_region_y) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.crv.click_add_point(context, x,y) return 'main' if event.type == 'RIGHTMOUSE' and event.value == 'PRESS': self.crv.click_delete_point(mode = 'mouse') return 'main' if event.type == 'X' and event.value == 'PRESS': self.crv.delete_selected(mode = 'selected') return 'main' if event.type == 'RET' and event.value == 'PRESS': return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal_grab(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.crv.grab_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.crv.grab_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.crv.grab_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'grab' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['grab'] = self.modal_grab 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: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self,context, event): if context.object: ob = context.object L = odcutils.get_bbox_center(ob, world=True) context.scene.cursor_location = L self.crv = CurveDataManager(context,snap_type ='SCENE', snap_object = None, shrink_mod = False, name = 'Plan Curve') #TODO, tweak the modifier as needed help_txt = "DRAW ARCH OUTLINE\n\nLeft Click in scene to draw a curve \nPoints will snap to objects under mouse \nNot clicking on object will make points at same depth as 3D cursor \n Right click to delete a point n\ G to grab \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(arch_crv_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) 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'}
class D3SPLINT_OT_live_insertion_axis(bpy.types.Operator): """Pick Insertin Axis by viewing model from occlusal""" bl_idname = "d3splint.live_insertion_axis" bl_label = "Pick Insertion Axis" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return True 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_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 {'NUMPAD_1', 'NUMPAD_3', "NUMPAD_7" } and event.value == 'PRESS': return 'nav' if "ARROW" in event.type and event.value == 'PRESS': self.rotate_arrow(context, event) return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y res = self.click_model(context, x, y) if res: self.preview_direction(context) return 'main' if event.type == 'P' and event.value == 'PRESS': self.preview_direction(context) return 'main' if event.type == 'RET' and event.value == 'PRESS': self.finish(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': return 'cancel' return 'main' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = 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'}: #context.space_data.show_manipulator = True #if nmode == 'finish': # context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'} #else: # context.space_data.transform_manipulators = {'TRANSLATE'} #clean up callbacks bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') return {'FINISHED'} if nmode == 'finish' else {'CANCELLED'} if nmode: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self, context, event): if len(context.scene.odc_splints) == 0: self.report({'ERROR'}, "Need to mark splint and opposing models first") return {'CANCELLED'} n = context.scene.odc_splint_index self.splint = context.scene.odc_splints[n] self.previewed = False max_model = self.splint.get_maxilla() mand_model = self.splint.get_mandible() if self.splint.jaw_type == 'MANDIBLE': Model = bpy.data.objects.get(mand_model) else: Model = bpy.data.objects.get(max_model) for ob in bpy.data.objects: ob.select = False ob.hide = True Model.select = True Model.hide = False context.scene.objects.active = Model add_volcolor_material_to_obj(Model, 'Undercut') #view a presumptive occlusal axis if self.splint.jaw_type == 'MAXILLA': bpy.ops.view3d.viewnumpad(type='BOTTOM') else: bpy.ops.view3d.viewnumpad(type='TOP') #add in a insertin axis direction loc = odcutils.get_bbox_center(Model, world=True) view = context.space_data.region_3d.view_rotation * Vector((0, 0, 1)) mxT = Matrix.Translation(loc) mxR = context.space_data.region_3d.view_rotation.to_matrix().to_4x4() if "Insertion Axis" in bpy.data.objects: ins_ob = bpy.data.objects.get('Insertion Axis') else: ins_ob = bpy.data.objects.new('Insertion Axis', None) ins_ob.empty_draw_type = 'SINGLE_ARROW' ins_ob.empty_draw_size = 20 context.scene.objects.link(ins_ob) ins_ob.hide = False ins_ob.parent = Model ins_ob.matrix_world = mxT * mxR self.ins_ob = ins_ob #get bmesh data to process self.bme = bmesh.new() self.bme.from_mesh(Model.data) self.bme.verts.ensure_lookup_table() self.bme.edges.ensure_lookup_table() self.bme.faces.ensure_lookup_table() self.model = Model bpy.ops.view3d.view_selected() context.space_data.viewport_shade = 'SOLID' context.space_data.show_textured_solid = True #TODO, tweak the modifier as needed help_txt = "Pick Insertion Axis\n\n- Position your viewing direction looking onto the model\n- LEFT CLICK on the model\n- You can then rotate and pan your view to assess the undercuts. This process can be repeated until the desired insertion axis is chosen.\n\nADVANCED USE\n\n- Use LEFT_ARROW, RIGHT_ARROW, UP_ARROW and DOWN_ARROW to accurately alter the axis. Holding SHIFT while pressing the ARROW keys will alter the axis by 0.5 degrees.\nPress ENTER when finished" 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( pick_axis_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} def rotate_arrow(self, context, event): loc = Matrix.Translation(self.ins_ob.matrix_world.to_translation()) rot_base = self.ins_ob.matrix_world.to_3x3() r_model = self.model.matrix_world.to_quaternion() if event.type == "UP_ARROW": axis = r_model * Vector((0, 1, 0)) if event.type == "DOWN_ARROW": axis = r_model * Vector((0, -1, 0)) if event.type == "LEFT_ARROW": axis = r_model * Vector((1, 0, 0)) if event.type == "RIGHT_ARROW": axis = r_model * Vector((-1, 0, 0)) if event.shift: ang = .5 * math.pi / 180 else: ang = 2.5 * math.pi / 180 rot = Matrix.Rotation(ang, 3, axis) self.ins_ob.matrix_world = loc * (rot * rot_base).to_4x4() view = self.ins_ob.matrix_world.to_quaternion() * Vector((0, 0, 1)) view_local = self.model.matrix_world.inverted().to_quaternion() * view fs_undercut = bme_undercut_faces(self.bme, view_local) vcolor_data = self.bme.loops.layers.color['Undercut'] bmesh_color_bmfaces(self.bme.faces[:], vcolor_data, Color((1, 1, 1))) bmesh_color_bmfaces(fs_undercut, vcolor_data, Color((.8, .2, .5))) self.bme.to_mesh(self.model.data) return def click_model(self, context, x, y): region = context.region rv3d = context.region_data coord = x, y ray_max = 10000 view_vector = region_2d_to_vector_3d(region, rv3d, coord) ray_origin = region_2d_to_origin_3d(region, rv3d, coord) ray_target = ray_origin + (view_vector * ray_max) imx = self.model.matrix_world.inverted() result, loc, normal, idx = self.model.ray_cast( imx * ray_origin, imx * ray_target - imx * ray_origin) return result def preview_direction(self, context): start = time.time() view = context.space_data.region_3d.view_rotation * Vector((0, 0, 1)) mx = self.model.matrix_world i_mx = mx.inverted() view_local = i_mx.to_quaternion() * view fs_undercut = bme_undercut_faces(self.bme, view_local) print('there are %i undercts' % len(fs_undercut)) vcolor_data = self.bme.loops.layers.color['Undercut'] bmesh_color_bmfaces(self.bme.faces[:], vcolor_data, Color((1, 1, 1))) bmesh_color_bmfaces(fs_undercut, vcolor_data, Color((.8, .2, .5))) self.bme.to_mesh(self.model.data) finish = time.time() print('took %s to detect undercuts' % str(finish - start)[0:4]) loc = odcutils.get_bbox_center(self.model, world=True) mxT = Matrix.Translation(loc) mxR = context.space_data.region_3d.view_rotation.to_matrix().to_4x4() self.ins_ob.matrix_world = mxT * mxR self.previewed = True return def finish(self, context): loc = odcutils.get_bbox_center(self.model, world=True) ins_ob = bpy.data.objects.get('Insertion Axis') view = ins_ob.matrix_world.to_quaternion() * Vector((0, 0, 1)) #view = context.space_data.region_3d.view_rotation * Vector((0,0,1)) odcutils.silouette_brute_force(context, self.model, view, True) #mxT = Matrix.Translation(loc) #mxR = context.space_data.region_3d.view_rotation.to_matrix().to_4x4() #if "Insertion Axis" in bpy.data.objects: # ob = bpy.data.objects.get('Insertion Axis') # ob.hide = False #else: # ob = bpy.data.objects.new('Insertion Axis', None) # ob.empty_draw_type = 'SINGLE_ARROW' # ob.empty_draw_size = 20 # context.scene.objects.link(ob) bpy.ops.object.select_all(action='DESELECT') #ob.parent = self.model #ob.matrix_world = mxT * mxR context.scene.objects.active = self.model self.model.select = True #context.scene.cursor_location = loc #bpy.ops.view3d.view_center_cursor() #bpy.ops.view3d.viewnumpad(type = 'FRONT') #bpy.ops.view3d.view_selected() #context.space_data.transform_manipulators = {'ROTATE'} for i, mat in enumerate(self.model.data.materials): if mat.name == 'Undercut': break self.model.data.materials.pop(i, update_data=True) context.space_data.show_textured_solid = False self.splint.insertion_path = True self.model.lock_location[0], self.model.lock_location[ 1], self.model.lock_location[2] = True, True, True
class OPENDENTAL_OT_place_bracket(bpy.types.Operator): """Place Bracket on surface of selected object""" bl_idname = "opendental.place_ortho_bracket" bl_label = "Ortho Bracket Place" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): if context.mode == "OBJECT" and context.object != None: return True else: return False 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_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 == 'G' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'grab' if event.type == 'T' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'torque' if event.type == 'R' and event.value == 'PRESS' and self.bracket_slicer: self.bracket_slicer.prepare_slice() return 'rotate' if event.type == 'MOUSEMOVE': return 'main' if event.type == 'LEFTMOUSE' and event.value == 'PRESS': x, y = event.mouse_region_x, event.mouse_region_y self.bracket_manager.place_bracket(context, x,y) return 'main' if event.type == 'RET' and event.value == 'PRESS': if self.bracket_slicer: self.bracket_slicer.cache_slice_to_grease(context) return 'finish' elif event.type == 'ESC' and event.value == 'PRESS': del_obj = self.bracket_manager.bracket_obj context.scene.objects.unlink(del_obj) bpy.data.objects.remove(del_obj) return 'cancel' return 'main' def modal_torque(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'torque' elif event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW','DOWN_ARROW'}: self.bracket_manager.torque_event(event.type, event.shift) self.bracket_slicer.slice() return 'torque' def modal_rotate(self,context,event): # no navigation in grab mode if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'rotate' elif event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW','DOWN_ARROW'}: self.bracket_manager.rotate_event(event.type, event.shift) self.bracket_slicer.slice() return 'rotate' def modal_grab(self,context,event): # no navigation in grab mode #uses the slicer to manage the grab if event.type == 'LEFTMOUSE' and event.value == 'PRESS': #confirm location self.bracket_slicer.slice_confirm() return 'main' elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS': #put it back! self.bracket_slicer.slice_cancel() return 'main' elif event.type == 'MOUSEMOVE': #update the b_pt location self.bracket_slicer.slice_mouse_move(context,event.mouse_region_x, event.mouse_region_y) return 'grab' elif event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'UP_ARROW','DOWN_ARROW'}: self.bracket_manager.spin_event(event.type, event.shift) self.bracket_slicer.slice() return 'grab' def modal(self, context, event): context.area.tag_redraw() FSM = {} FSM['main'] = self.modal_main FSM['rotate'] = self.modal_rotate FSM['grab'] = self.modal_grab FSM['torque'] = self.modal_torque 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: self.mode = nmode return {'RUNNING_MODAL'} def invoke(self, context, event): sce=bpy.context.scene if context.object and context.object.type == 'MESH': self.bracket_manager = BracketDataManager(context,snap_type ='OBJECT', snap_object = context.object, name = 'Bracket') self.bracket_slicer = BracektSlicer(context, self.bracket_manager) else: self.bracket_manager = BracketDataManager(context,snap_type ='SCENE', snap_object = None, name = 'Bracket') self.bracket_slicer = None help_txt = "DRAW MARGIN OUTLINE\n\nLeft Click on model to place bracket.\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(bracket_placement_draw_callback, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'}