Skip to content
Snippets Groups Projects
xedit_free_rotate.py 57.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • NBurn's avatar
    NBurn committed
            closest_pt, self.overlap_idx = closest_to_point(self.mouse_co, pts2d)
            pts2d[self.grab_pt] = self.mouse_co
            ms_colr = self.pts[self.grab_pt].colr
            if not self.shift_held:
                draw_line_2d(line_beg, self.mouse_co, self.pts[self.grab_pt].colr)
                draw_pt_2d(closest_pt, Colr.white, ptsz_lrg)
    
        elif self.mod_pt is not None:
            ms_colr = self.pts[self.mod_pt].colr
            m_pts2d = [loc3d_to_reg2d(reg, rv3d, p) for p in self.multi_tmp.ls]
            closest_pt, self.overlap_idx = closest_to_point(self.mouse_co, m_pts2d)
            draw_pt_2d(pts2d[self.mod_pt], Colr.white, ptsz_lrg)
            if self.shift_held:
                draw_pt_2d(self.mouse_co, Colr.black, ptsz_lrg)
                if len(m_pts2d) > 1:
                    for mp in m_pts2d:
                        draw_pt_2d(mp, Colr.black, ptsz_lrg)
            else:
                draw_pt_2d(closest_pt, Colr.black, ptsz_lrg)
            if len(m_pts2d) > 1:
                for p in m_pts2d:
                    draw_pt_2d(p, ms_colr, ptsz_sml)
            last_mod_pt = loc3d_to_reg2d(reg, rv3d, self.multi_tmp.ls[-1])
            draw_line_2d(last_mod_pt, self.mouse_co, self.pts[self.mod_pt].colr)
    
        else:  # "Normal" mode
            closest_pt, self.overlap_idx = closest_to_point(self.mouse_co, pts2d)
            if self.shift_held:
                draw_pt_2d(closest_pt, Colr.white, ptsz_lrg)
            else:
                draw_pt_2d(closest_pt, Colr.black, ptsz_lrg)
    
            rwid = context.region.width
            rhgt = context.region.height
            if self.pt_cnt == 1:
    
                if TransDat.axis_lock is not None:
    
                    if not self.running_transf:
    
    NBurn's avatar
    NBurn committed
                        self.rotate_btn.draw_btn(pts2d[0], self.mouse_co)
                        self.rotate_btn.is_drawn = True
    
    
                    if TransDat.axis_lock == 'X':
    
    NBurn's avatar
    NBurn committed
                        test = self.pts[0].co3d + Vector((1, 0, 0))
                        colr = Colr.red
    
                    elif TransDat.axis_lock == 'Y':
    
    NBurn's avatar
    NBurn committed
                        test = self.pts[0].co3d + Vector((0, 1, 0))
                        colr = Colr.green
    
                    elif TransDat.axis_lock == 'Z':
    
    NBurn's avatar
    NBurn committed
                        test = self.pts[0].co3d + Vector((0, 0, 1))
                        colr = Colr.blue
    
                    t2d = loc3d_to_reg2d(reg, rv3d, test)
                    axis_pts = get_axis_line_co(pts2d[0], t2d, rwid, rhgt)
                    if axis_pts is not None:
                        draw_line_2d(axis_pts[0], axis_pts[1], colr)
    
    
                    dpi = bpy.context.preferences.system.dpi
    
    NBurn's avatar
    NBurn committed
                    font_id, txt_sz = 0, 32
                    x_pos, y_pos = self.rtoolsw + 80, 36
    
                    #bgl.glColor4f(*colr)
                    blf.color(font_id, *colr)
    
    NBurn's avatar
    NBurn committed
                    blf.size(font_id, txt_sz, dpi)
                    blf.position(font_id, x_pos, y_pos, 0)
    
                    blf.draw(font_id, TransDat.axis_lock)
    
    NBurn's avatar
    NBurn committed
    
            elif self.pt_cnt == 2:
                axis_pts = get_axis_line_co(pts2d[0], pts2d[1], rwid, rhgt)
                #draw_line_2d(pts2d[0], pts2d[1], Colr.white)
                if axis_pts is not None:
                    draw_line_2d(axis_pts[0], axis_pts[1], Colr.white)
                    #draw_line_2d(pts2d[0], self.mouse_co, Colr.white)
                    btn_co = pts2d[0].lerp(pts2d[1], 0.5)
                    #self.meas_btn.draw_btn(btn_co, self.mouse_co)
                    #self.meas_btn.active = True
                    if not self.running_transf:
                        self.rotate_btn.draw_btn(btn_co, self.mouse_co)
                        self.rotate_btn.is_drawn = True
            elif self.pt_cnt == 3:
    
                test = self.pts[2].co3d + TransDat.piv_norm
    
    NBurn's avatar
    NBurn committed
                t2d = loc3d_to_reg2d(reg, rv3d, test)
                axis_pts = get_axis_line_co(pts2d[2], t2d, rwid, rhgt)
                if axis_pts is not None:
                    draw_line_2d(axis_pts[0], axis_pts[1], Colr.white)
    
    NBurn's avatar
    NBurn committed
                #btn_co = pts2d[2] + Vector((0, 20))
                draw_line_2d(pts2d[0], pts2d[2], Colr.white)
                draw_line_2d(pts2d[1], pts2d[2], Colr.white)
                #self.meas_btn.draw_btn(pts2d[2], self.mouse_co)
                #self.meas_btn.active = True
                #draw_btn(self, btn_loc, mouse_co):
                if not self.running_transf:
                    self.rotate_btn.draw_btn(pts2d[2], self.mouse_co)
                    self.rotate_btn.is_drawn = True
    
        # todo : figure out reason for weirdness below
        cnt = 0
        for p in pts2d:
            draw_pt_2d(p, self.pts[cnt].colr, ptsz_sml)
            cnt += 1
    
        if self.highlight_mouse and not self.running_transf:
            draw_pt_2d(self.mouse_co, ms_colr, ptsz_sml)
    
        self.menu.draw(self.rotate_btn.is_drawn)
    
    
    def exit_addon(self):
        restore_blender_settings(self.settings_backup)
    
        bpy.context.area.header_text_set(None)
    
    NBurn's avatar
    NBurn committed
        # todo : reset openGL settings?
        #bgl.glColor4f()
        #blf.size()
        #blf.position()
        #print("\n\nAdd-On Exited\n")  # debug
    
    
    
    # Checks if "use_region_overlap" is enabled and X offset is needed.
    
    NBurn's avatar
    NBurn committed
    def get_reg_overlap():
        rtoolsw = 0  # region tools (toolbar) width
        #ruiw = 0  # region ui (Number/n-panel) width
    
        system = bpy.context.preferences.system
    
    NBurn's avatar
    NBurn committed
        if system.use_region_overlap:
    
            area = bpy.context.area
            for r in area.regions:
                if r.type == 'TOOLS':
                    rtoolsw = r.width
                    #elif r.type == 'UI':
                    #    ruiw = r.width
    
    NBurn's avatar
    NBurn committed
        #return rtoolsw, ruiw
        return rtoolsw
    
    
    
    class XEDIT_OT_free_rotate(bpy.types.Operator):
    
    NBurn's avatar
    NBurn committed
        bl_idname = "view3d.xedit_free_rotate_op"
    
        bl_label = "Exact Edit Free Rotate"
    
    NBurn's avatar
    NBurn committed
    
        # Only launch Add-On from OBJECT or EDIT modes
        @classmethod
        def poll(self, context):
            return context.mode == 'OBJECT' or context.mode == 'EDIT_MESH'
    
        def modal(self, context, event):
            context.area.tag_redraw()
    
            if event.type in {'A', 'MIDDLEMOUSE', 'WHEELUPMOUSE',
            'WHEELDOWNMOUSE', 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4',
            'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_0', 'TAB'}:
                return {'PASS_THROUGH'}
    
            if event.type == 'MOUSEMOVE':
                self.mouse_co = Vector((event.mouse_region_x, event.mouse_region_y))
    
            if event.type in {'LEFT_SHIFT', 'RIGHT_SHIFT'}:
                if event.value == 'PRESS':
                    self.shift_held = True
                    #print("\nShift pressed")  # debug
                elif event.value == 'RELEASE':
                    self.shift_held = False
                    #print("\nShift released")  # debug
    
            if event.type == 'RIGHTMOUSE':
                if event.value == 'PRESS':
                    if self.lmb_held:
                        bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
                        exit_addon(self)
                        return {'CANCELLED'}
                elif event.value == 'RELEASE':
                    self.running_transf = False
                    set_mouse_highlight(self)
                    set_help_text(self, "CLICK")
                return {'PASS_THROUGH'}
    
            elif event.type == 'SPACE' and event.value == 'RELEASE':
                # Safely exit transform
                if self.running_transf:
                    self.running_transf = False
    
            elif event.type in {'RET', 'LEFTMOUSE'} and event.value == 'PRESS':
                self.mouse_co = Vector((event.mouse_region_x, event.mouse_region_y))
                if event.type == 'LEFTMOUSE':
                    self.lmb_held = True
                #print("LEFTMOUSE PRESS")  # debug
    
            elif event.type in {'RET', 'LEFTMOUSE'} and event.value == 'RELEASE':
                # prevent click/enter that launched add-on from doing anything
                if self.first_run:
                    self.first_run = False
                    return {'RUNNING_MODAL'}
                if event.type == 'LEFTMOUSE':
                    self.lmb_held = False
                #print("LeftMouse released")  # debug
                self.mouse_co = Vector((event.mouse_region_x, event.mouse_region_y))
    
                #===========================
                # Safely exit transform
                #===========================
    
                if self.running_transf:
    
    NBurn's avatar
    NBurn committed
                    self.running_transf = False
    
                #===================================
                # Check for click on Rotate Button
                #===================================
    
                elif self.rotate_btn.is_drawn and self.rotate_btn.ms_over:
    
    NBurn's avatar
    NBurn committed
                    #print("Button Clicked")
                    curs_loc = None
    
                    rot_axis = None
    
    NBurn's avatar
    NBurn committed
                    #bpy.ops.object.ms_input_dialog_op('INVOKE_DEFAULT')
                    if self.pt_cnt == 1:
    
                        if TransDat.axis_lock == 'X':
    
                            rot_axis = Vector((1.0, 0.0, 0.0))
    
                        elif TransDat.axis_lock == 'Y':
    
                            rot_axis = Vector((0.0, 1.0, 0.0))
    
                        elif TransDat.axis_lock == 'Z':
    
    NBurn's avatar
    NBurn committed
                            # -1 because it is assumed most rotations
                            # will have negative z pointing down
    
                            rot_axis = Vector((0.0, 0.0, -1.0))
    
    NBurn's avatar
    NBurn committed
                        curs_loc = self.pts[0].co3d.copy()
                    elif self.pt_cnt == 2:
    
                        #if TransDat.axis_lock is None:
    
    NBurn's avatar
    NBurn committed
                        rot_vec = self.pts[1].co3d - self.pts[0].co3d
                        rot_axis = rot_vec.normalized()
                        curs_loc = self.pts[0].co3d.lerp(self.pts[1].co3d, 0.5)
                    elif self.pt_cnt == 3:
    
                        #if TransDat.axis_lock is None:
                        rot_axis = TransDat.piv_norm
    
    NBurn's avatar
    NBurn committed
                        curs_loc = self.pts[2].co3d.copy()
    
                    o_mat = create_z_orient(rot_axis)
    
    NBurn's avatar
    NBurn committed
                    self.running_transf = True
    
                    bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
    
                    bpy.context.scene.cursor.location = curs_loc
    
                    #bpy.ops.transform.rotate('INVOKE_DEFAULT', axis=rot_axis)
    
                    bpy.ops.transform.rotate('INVOKE_DEFAULT', orient_matrix=o_mat,
                            orient_axis='Z', constraint_axis=(False, False, True))
    
    NBurn's avatar
    NBurn committed
    
                #===========================================
                # Check for click on "Add Selected" Button
                #===========================================
                elif self.add_rm_btn.ms_over:
                    if self.mod_pt is not None:
                        if not self.shift_held:
                            add_select_multi(self)
                        else:
                            if self.pt_cnt < 3:
                                new_select_multi(self)
                                exit_multi_mode(self)
                                self.menu.change_menu(self.pt_cnt)
                    elif self.grab_pt is not None:
                        co3d = None
                        if bpy.context.mode == "OBJECT":
                            if len(bpy.context.selected_objects) > 0:
                                if not self.shift_held:
                                    co3d = bpy.context.selected_objects[0].location
                                else:
                                    new_select_multi(self)
                                    exit_multi_mode(self)
                                    self.menu.change_menu(self.pt_cnt)
                        elif bpy.context.mode == "EDIT_MESH":
                            m_w = bpy.context.edit_object.matrix_world
                            bm = bmesh.from_edit_mesh(bpy.context.edit_object.data)
                            if len(bm.select_history) > 0:
                                if not self.shift_held:
                                    for sel in bm.select_history:
                                        if type(sel) is bmesh.types.BMVert:
    
                                            co3d = m_w @ sel.co
    
    NBurn's avatar
    NBurn committed
                                            break
                                        elif type(sel) is bmesh.types.BMEdge or \
                                                type(sel) is bmesh.types.BMFace:
                                            co3d = Vector()
                                            for v in sel.verts:
    
                                                co3d += m_w @ v.co
    
    NBurn's avatar
    NBurn committed
                                            co3d = co3d / len(sel.verts)
                                            break
                                else:
                                    new_select_multi(self)
                                    exit_multi_mode(self)
                                    self.menu.change_menu(self.pt_cnt)
    
                        if co3d is not None:
                            if not in_ref_pts(self, co3d):
                                self.pts[self.grab_pt].co3d = co3d
                            else:
                                swap_ref_pts(self, self.grab_pt, self.swap_pt)
                                self.swap_pt = None
                        self.grab_pt = None
                        updatelock_pts(self, self.pts)
                        set_piv(self)
                    else:  # no grab or mod point
                        if self.shift_held:
                            if self.pt_cnt < 3:
                                new_select_multi(self)
                                if in_ref_pts(self, self.multi_tmp.get_co(), self.mod_pt):
                                    self.report({'WARNING'}, 'Points overlap.')
                                self.pts[self.mod_pt].co3d = self.multi_tmp.get_co()
                                self.menu.change_menu(self.pt_cnt)
                        else:
                            add_select(self)
                    # todo : see if this is really a good solution...
                    if self.mod_pt is None:
                        set_help_text(self, "CLICK")
                    else:
                        set_help_text(self, "MULTI")
    
                #===========================
                # Point Place or Grab Mode
                #===========================
                elif self.mod_pt is None:
                    if self.overlap_idx is None:  # no point overlap
                        if not self.shift_held:
                            if self.grab_pt is not None:
                                found_pt = find_closest_point(self.mouse_co)
                                if found_pt is not None:
                                    if not in_ref_pts(self, found_pt):
                                        self.pts[self.grab_pt].co3d = found_pt
                                self.grab_pt = None
                                if self.pt_cnt > 1:
                                    updatelock_pts(self, self.pts)
                                set_mouse_highlight(self)
                                set_piv(self)
                                set_help_text(self, "CLICK")
                            elif self.pt_cnt < 3:
                                found_pt = find_closest_point(self.mouse_co)
                                if found_pt is not None:
                                    if not in_ref_pts(self, found_pt):
                                        self.pts[self.pt_cnt].co3d = found_pt
                                        self.pt_cnt += 1
                                        self.menu.change_menu(self.pt_cnt)
                                        if self.pt_cnt > 1:
    
                                            TransDat.axis_lock = None
    
    NBurn's avatar
    NBurn committed
                                            updatelock_pts(self, self.pts)
                                            set_piv(self)
                                            #if self.pt_cnt
                                        set_mouse_highlight(self)
                                        set_help_text(self, "CLICK")
                                        ''' Begin Debug
                                        cnt = self.pt_cnt - 1
                                        pt_fnd_str = str(self.pts[cnt].co3d)
                                        pt_fnd_str = pt_fnd_str.replace("<Vector ", "Vector(")
                                        pt_fnd_str = pt_fnd_str.replace(">", ")")
                                        print("ref_pt_" + str(cnt) + ' =', pt_fnd_str)
                                        #print("ref pt added:", self.cnt, "cnt:", self.cnt+1)
                                        End Debug '''
                    else:  # overlap
                        if self.grab_pt is not None:
                            if not self.shift_held:
                                if self.grab_pt != self.overlap_idx:
                                    swap_ref_pts(self, self.grab_pt, self.overlap_idx)
                                self.grab_pt = None
                                if self.pt_cnt > 1:
                                    updatelock_pts(self, self.pts)
                                set_mouse_highlight(self)
                                set_piv(self)
                                set_help_text(self, "CLICK")
    
                        elif not self.shift_held:
                            # overlap and shift not held == remove point
                            rem_ref_pt(self, self.overlap_idx)
                            set_help_text(self, "CLICK")
                        else:  # shift_held
                            # enable multi point mode
                            self.mod_pt = self.overlap_idx
                            self.multi_tmp.reset(self.pts[self.mod_pt].co3d)
                            self.highlight_mouse = True
                            set_help_text(self, "MULTI")
    
                #===========================
                # Mod Ref Point Mode
                #===========================
                else:  # mod_pt exists
                    if self.overlap_idx is None:  # no point overlap
                        if not self.shift_held:
                            # attempt to add new point to multi_tmp
                            found_pt = find_closest_point(self.mouse_co)
                            if found_pt is not None:
                                self.multi_tmp.try_add(found_pt)
                                mult_co3d = self.multi_tmp.get_co()
                                if in_ref_pts(self, mult_co3d, self.mod_pt):
                                    self.report({'WARNING'}, 'Points overlap.')
                                self.pts[self.mod_pt].co3d = mult_co3d
                        else:  # shift_held, exit multi_tmp
                            exit_multi_mode(self)
                            set_piv(self)
                    else:  # overlap multi_tmp
                        if not self.shift_held:
                            # remove multi_tmp point
                            self.multi_tmp.rem_pt(self.overlap_idx)
                            # if all multi_tmp points removed,
                            # exit multi mode, remove edited point
                            if self.multi_tmp.co3d is None:
                                rem_ref_pt(self, self.mod_pt)
                                self.mod_pt = None
                                set_help_text(self, "CLICK")
                            elif in_ref_pts(self, self.multi_tmp.co3d, self.mod_pt):
                                self.report({'WARNING'}, 'Points overlap.')
                                self.pts[self.mod_pt].co3d = self.multi_tmp.get_co()
                            else:
                                self.pts[self.mod_pt].co3d = self.multi_tmp.get_co()
                        else:  # shift_held
                            exit_multi_mode(self)
    
            if event.type == 'C' and event.value == 'PRESS':
                #print("Pressed C\n")  # debug
                axis_key_check(self, None)
    
            elif event.type == 'X' and event.value == 'PRESS':
                #print("Pressed X\n")  # debug
                axis_key_check(self, 'X')
    
            elif event.type == 'Y' and event.value == 'PRESS':
                #print("Pressed Y\n")  # debug
                axis_key_check(self, 'Y')
    
            elif event.type == 'Z' and event.value == 'PRESS':
                #print("Pressed Z\n")  # debug
                axis_key_check(self, 'Z')
    
                '''
    
            elif event.type == 'D' and event.value == 'RELEASE':
                # open debug console
                __import__('code').interact(local=dict(globals(), **locals()))
    
    NBurn's avatar
    NBurn committed
                '''
    
            elif event.type == 'G' and event.value == 'RELEASE':
                # if already in grab mode, cancel grab
                if self.grab_pt is not None:
                    self.grab_pt = None
                    set_mouse_highlight(self)
                    set_help_text(self, "CLICK")
                # else enable grab mode (if possible)
                elif self.mod_pt is None:
                    if self.overlap_idx is not None:
                        self.grab_pt = self.overlap_idx
                        self.highlight_mouse = False
                        set_help_text(self, "GRAB")
    
            elif event.type in {'ESC'} and event.value == 'RELEASE':
                bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
                exit_addon(self)
                return {'CANCELLED'}
    
            if self.force_quit:
                bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
                exit_addon(self)
                return {'FINISHED'}
    
            return {'RUNNING_MODAL'}
    
        def invoke(self, context, event):
            if context.area.type == 'VIEW_3D':
                args = (self, context)
    
                # Add the region OpenGL drawing callback
                # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
    
                self._handle = bpy.types.SpaceView3D.draw_handler_add(
                        draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
    
    NBurn's avatar
    NBurn committed
    
                self.settings_backup = backup_blender_settings()
                self.mouse_co = Vector((event.mouse_region_x, event.mouse_region_y))
                self.rtoolsw = get_reg_overlap()  # region tools (toolbar) width
                self.highlight_mouse = True  # draw ref point on mouse
                self.pts = []
                self.running_transf = False
                self.pt_cnt = 0
                self.lk_pts = []
                self.multi_tmp = TempPoint()
                self.rotate_btn = ViewButton(Colr.red, Colr.white, 18, Colr.white, (0.0, 20))
                self.rotate_btn.set_text("Rotate")
                self.add_rm_btn = ViewButton(Colr.red, Colr.white, 18, Colr.white, (190, 36))
                self.overlap_idx = None
                self.shift_held = False
                #self.debug_flag = False
                self.mod_pt = None
                self.first_run = event.type in {'RET', 'LEFTMOUSE'} and event.value != 'RELEASE'
                self.force_quit = False
                self.grab_pt = None
                self.new_free_co = ()
                self.swap_pt = None
                #self.addon_mode = CLICK_CHECK
                self.lmb_held = False
    
    
                self.menu = MenuHandler("Free Rotate", 18, Colr.yellow, \
                        Colr.white, self.rtoolsw, context.region)
    
    NBurn's avatar
    NBurn committed
                self.menu.add_menu(["Axis Lock Rotate"])
                self.menu.add_menu(["Axis Rotate"])
                self.menu.add_menu(["Planar Rotate"])
    
                context.window_manager.modal_handler_add(self)
    
                init_blender_settings()
    
                init_ref_pts(self)
                set_transform_data_none()
    
    NBurn's avatar
    NBurn committed
                editmode_refresh()
                #print("Add-on started")  # debug
                self.add_rm_btn.set_text("Add Selected")
                set_help_text(self, "CLICK")
    
                return {'RUNNING_MODAL'}
            else:
                self.report({'WARNING'}, "View3D not found, cannot run operator")
                return {'CANCELLED'}