diff --git a/invesalius/data/styles.py b/invesalius/data/styles.py index fbf698c..8219e41 100644 --- a/invesalius/data/styles.py +++ b/invesalius/data/styles.py @@ -77,6 +77,8 @@ WATERSHED_OPERATIONS = {_("Erase"): BRUSH_ERASE, class BaseImageInteractorStyle(vtk.vtkInteractorStyleImage): def __init__(self, viewer): + self.viewer = viewer + self.right_pressed = False self.left_pressed = False self.middle_pressed = False @@ -110,6 +112,22 @@ class BaseImageInteractorStyle(vtk.vtkInteractorStyleImage): def OnMiddleButtonReleaseEvent(self, evt, obj): self.middle_pressed = False + def GetMousePosition(self): + mx, my = self.viewer.get_vtk_mouse_position() + return mx, my + + def GetPickPosition(self, mouse_position=None): + if mouse_position is None: + mx, my = self.GetMousePosition() + else: + mx, my = mouse_position + iren = self.viewer.interactor + render = iren.FindPokedRenderer(mx, my) + self.picker.Pick(mx, my, 0, render) + x, y, z = self.picker.GetPickPosition() + return (x, y, z) + + class DefaultInteractorStyle(BaseImageInteractorStyle): """ @@ -277,7 +295,7 @@ class BaseImageEditionInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) @@ -316,7 +334,7 @@ class BaseImageEditionInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) operation = self.config.operation @@ -486,7 +504,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): self.ChangeCrossPosition(iren) def ChangeCrossPosition(self, iren): - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() x, y, z = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) self.viewer.UpdateSlicesPosition([x, y, z]) # This "Set cross" message is needed to update the cross in the other slices @@ -626,7 +644,7 @@ class WWWLInteractorStyle(DefaultInteractorStyle): def OnWindowLevelMove(self, obj, evt): if (self.left_pressed): iren = obj.GetInteractor() - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() self.acum_achange_window += mouse_x - self.last_x self.acum_achange_level += mouse_y - self.last_y self.last_x, self.last_y = mouse_x, mouse_y @@ -713,7 +731,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): def OnInsertMeasurePoint(self, obj, evt): slice_number = self.slice_data.number x, y, z = self._get_pos_clicked() - mx, my = self.viewer.interactor.GetEventPosition() + mx, my = self.GetMousePosition() if self.selected: self.selected = None @@ -784,7 +802,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): self.viewer.UpdateCanvas() else: - mx, my = self.viewer.interactor.GetEventPosition() + mx, my = self.GetMousePosition() if self._verify_clicked_display(mx, my): self.viewer.interactor.SetCursor(wx.Cursor(wx.CURSOR_HAND)) else: @@ -801,12 +819,8 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): self.viewer.scroll_enabled = True def _get_pos_clicked(self): - iren = self.viewer.interactor - mx,my = iren.GetEventPosition() - render = iren.FindPokedRenderer(mx, my) self.picker.AddPickList(self.slice_data.actor) - self.picker.Pick(mx, my, 0, render) - x, y, z = self.picker.GetPickPosition() + x, y, z = self.GetPickPosition() self.picker.DeletePickList(self.slice_data.actor) return (x, y, z) @@ -919,11 +933,11 @@ class DensityMeasureStyle(DefaultInteractorStyle): def _pick_position(self): iren = self.viewer.interactor - mx, my = iren.GetEventPosition() + mx, my = self.GetMousePosition() return (mx, my) def _get_pos_clicked(self): - mouse_x, mouse_y = self._pick_position() + mouse_x, mouse_y = self.GetMousePosition() position = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) return position @@ -1232,15 +1246,14 @@ class EditorInteractorStyle(DefaultInteractorStyle): self.viewer.slice_data.cursor.Show(0) def SetUp(self): - x, y = self.viewer.interactor.ScreenToClient(wx.GetMousePosition()) if self.viewer.interactor.HitTest((x, y)) == wx.HT_WINDOW_INSIDE: self.viewer.slice_data.cursor.Show() - + y = self.viewer.interactor.GetSize()[1] - y w_x, w_y, w_z = self.viewer.get_coordinate_cursor(x, y, self.picker) self.viewer.slice_data.cursor.SetPosition((w_x, w_y, w_z)) - + self.viewer.interactor.SetCursor(wx.Cursor(wx.CURSOR_BLANK)) self.viewer.interactor.Render() @@ -1319,7 +1332,7 @@ class EditorInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) @@ -1351,7 +1364,7 @@ class EditorInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) @@ -1402,7 +1415,7 @@ class EditorInteractorStyle(DefaultInteractorStyle): iren = self.viewer.interactor viewer = self.viewer if iren.GetControlKey(): - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = self.viewer.get_slice_data(render) cursor = slice_data.cursor @@ -1420,7 +1433,7 @@ class EditorInteractorStyle(DefaultInteractorStyle): iren = self.viewer.interactor viewer = self.viewer if iren.GetControlKey(): - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = self.viewer.get_slice_data(render) cursor = slice_data.cursor @@ -1545,11 +1558,11 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): x, y = self.viewer.interactor.ScreenToClient(wx.GetMousePosition()) if self.viewer.interactor.HitTest((x, y)) == wx.HT_WINDOW_INSIDE: self.viewer.slice_data.cursor.Show() - + y = self.viewer.interactor.GetSize()[1] - y w_x, w_y, w_z = self.viewer.get_coordinate_cursor(x, y, self.picker) self.viewer.slice_data.cursor.SetPosition((w_x, w_y, w_z)) - + self.viewer.interactor.SetCursor(wx.Cursor(wx.CURSOR_BLANK)) self.viewer.interactor.Render() @@ -1622,7 +1635,7 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): iren = self.viewer.interactor viewer = self.viewer if iren.GetControlKey(): - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = self.viewer.get_slice_data(render) cursor = slice_data.cursor @@ -1640,7 +1653,7 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): iren = self.viewer.interactor viewer = self.viewer if iren.GetControlKey(): - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = self.viewer.get_slice_data(render) cursor = slice_data.cursor @@ -1663,7 +1676,7 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) @@ -1710,7 +1723,7 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): viewer._set_editor_cursor_visibility(1) - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() render = iren.FindPokedRenderer(mouse_x, mouse_y) slice_data = viewer.get_slice_data(render) @@ -2033,7 +2046,7 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): if self._over_center: self.dragging = True else: - x, y = self.viewer.interactor.GetEventPosition() + x, y = self.GetMousePosition() w, h = self.viewer.interactor.GetSize() self.picker.Pick(h/2.0, w/2.0, 0, self.viewer.slice_data.renderer) @@ -2063,7 +2076,7 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): else: # Getting mouse position iren = self.viewer.interactor - mx, my = iren.GetEventPosition() + mx, my = self.GetMousePosition() # Getting center value center = self.viewer.slice_.center @@ -2109,7 +2122,7 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): def _move_center_rot(self): iren = self.viewer.interactor - mx, my = iren.GetEventPosition() + mx, my = self.GetMousePosition() icx, icy, icz = self.viewer.slice_.center @@ -2131,7 +2144,7 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): def _rotate(self): # Getting mouse position iren = self.viewer.interactor - mx, my = iren.GetEventPosition() + mx, my = self.GetMousePosition() cx, cy, cz = self.viewer.slice_.center @@ -2294,7 +2307,7 @@ class FloodFillMaskInteractorStyle(DefaultInteractorStyle): viewer = self.viewer iren = viewer.interactor - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] @@ -2401,14 +2414,12 @@ class CropMaskInteractorStyle(DefaultInteractorStyle): Publisher.subscribe(self.CropMask, "Crop mask") def OnMove(self, obj, evt): - iren = self.viewer.interactor - x, y = iren.GetEventPosition() + x, y = self.GetMousePosition() self.draw_retangle.MouseMove(x,y) def OnLeftPressed(self, obj, evt): self.draw_retangle.mouse_pressed = True - iren = self.viewer.interactor - x, y = iren.GetEventPosition() + x, y = self.GetMousePosition() self.draw_retangle.LeftPressed(x,y) def OnReleaseLeftButton(self, obj, evt): @@ -2416,13 +2427,12 @@ class CropMaskInteractorStyle(DefaultInteractorStyle): self.draw_retangle.ReleaseLeft() def SetUp(self): - self.draw_retangle = geom.DrawCrop2DRetangle() self.draw_retangle.SetViewer(self.viewer) self.viewer.canvas.draw_list.append(self.draw_retangle) self.viewer.UpdateCanvas() - + if not(self.config.dlg_visible): self.config.dlg_visible = True @@ -2550,7 +2560,7 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle): return iren = self.viewer.interactor - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] @@ -2670,7 +2680,7 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): def do_2d_seg(self): viewer = self.viewer iren = viewer.interactor - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() x, y = self.viewer.get_slice_pixel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy() @@ -2736,7 +2746,7 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): def do_3d_seg(self): viewer = self.viewer iren = viewer.interactor - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.GetMousePosition() x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] diff --git a/invesalius/data/styles_3d.py b/invesalius/data/styles_3d.py index ca6e40a..08f31e2 100644 --- a/invesalius/data/styles_3d.py +++ b/invesalius/data/styles_3d.py @@ -300,8 +300,7 @@ class WWWLInteractorStyle(DefaultInteractorStyle): def OnWindowLevelMove(self, obj, evt): if self.changing_wwwl: - iren = obj.GetInteractor() - mouse_x, mouse_y = iren.GetEventPosition() + mouse_x, mouse_y = self.viewer.get_vtk_mouse_position() diff_x = mouse_x - self.last_x diff_y = mouse_y - self.last_y self.last_x, self.last_y = mouse_x, mouse_y @@ -311,8 +310,7 @@ class WWWLInteractorStyle(DefaultInteractorStyle): Publisher.sendMessage('Render volume viewer') def OnWindowLevelClick(self, obj, evt): - iren = obj.GetInteractor() - self.last_x, self.last_y = iren.GetLastEventPosition() + self.last_x, self.last_y = self.viewer.get_vtk_mouse_position() self.changing_wwwl = True def OnWindowLevelRelease(self, obj, evt): @@ -345,7 +343,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): Publisher.sendMessage("Remove incomplete measurements") def OnInsertLinearMeasurePoint(self, obj, evt): - x,y = self.viewer.interactor.GetEventPosition() + x,y = self.viewer.get_vtk_mouse_position() self.measure_picker.Pick(x, y, 0, self.viewer.ren) x, y, z = self.measure_picker.GetPickPosition() if self.measure_picker.GetActor(): @@ -384,7 +382,7 @@ class AngularMeasureInteractorStyle(DefaultInteractorStyle): Publisher.sendMessage("Remove incomplete measurements") def OnInsertAngularMeasurePoint(self, obj, evt): - x,y = self.viewer.interactor.GetEventPosition() + x,y = self.viewer.get_vtk_mouse_position() self.measure_picker.Pick(x, y, 0, self.viewer.ren) x, y, z = self.measure_picker.GetPickPosition() if self.measure_picker.GetActor(): @@ -412,7 +410,7 @@ class SeedInteractorStyle(DefaultInteractorStyle): self.AddObserver("LeftButtonPressEvent", self.OnInsertSeed) def OnInsertSeed(self, obj, evt): - x,y = self.viewer.interactor.GetEventPosition() + x,y = self.viewer.get_vtk_mouse_position() self.picker.Pick(x, y, 0, self.viewer.ren) point_id = self.picker.GetPointId() if point_id > -1: diff --git a/invesalius/data/viewer_slice.py b/invesalius/data/viewer_slice.py index e33e1ab..c319fee 100644 --- a/invesalius/data/viewer_slice.py +++ b/invesalius/data/viewer_slice.py @@ -650,6 +650,27 @@ class Viewer(wx.Panel): my = round((z - zi)/self.slice_.spacing[2], 0) return int(mx), int(my) + def get_vtk_mouse_position(self): + """ + Get Mouse position inside a wxVTKRenderWindowInteractorself. Return a + tuple with X and Y position. + Please use this instead of using iren.GetEventPosition because it's + not returning the correct values on Mac with HighDPI display, maybe + the same is happing with Windows and Linux, we need to test. + """ + mposx, mposy = wx.GetMousePosition() + cposx, cposy = self.interactor.ScreenToClient((mposx, mposy)) + mx, my = cposx, self.interactor.GetSize()[1] - cposy + if sys.platform == 'darwin': + # It's needed to mutiple by scale factor in HighDPI because of + # https://docs.wxpython.org/wx.glcanvas.GLCanvas.html + # For now we are doing this only on Mac but it may be needed on + # Windows and Linux too. + scale = self.interactor.GetContentScaleFactor() + mx *= scale + my *= scale + return int(mx), int(my) + def get_coordinate_cursor(self, mx, my, picker=None): """ Given the mx, my screen position returns the x, y, z position in world @@ -1028,7 +1049,7 @@ class Viewer(wx.Panel): #self.scroll.Bind(wx.EVT_SCROLL_ENDSCROLL, self.OnScrollBarRelease) self.interactor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.interactor.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) - # self.interactor.Bind(wx.EVT_SIZE, self.OnSize) + self.interactor.Bind(wx.EVT_SIZE, self.OnSize) def LoadImagedata(self, mask_dict): self.SetInput(mask_dict) @@ -1413,12 +1434,13 @@ class Viewer(wx.Panel): self.OnScrollBar() def OnSize(self, evt): - w, h = evt.GetSize() - w = float(w) - h = float(h) - if self.slice_data: - self.slice_data.SetSize((w, h)) - evt.Skip() + print("OnSize") + w, h = self.GetSize() + rwin = self.interactor.GetRenderWindow() + rwin.SetSize(w, h) + # if self.slice_data: + # self.slice_data.SetSize((w, h)) + # evt.Skip() def OnSetMIPSize(self, number_slices): self.number_slices = number_slices diff --git a/invesalius/data/viewer_volume.py b/invesalius/data/viewer_volume.py index aa75666..62ebf21 100644 --- a/invesalius/data/viewer_volume.py +++ b/invesalius/data/viewer_volume.py @@ -116,6 +116,9 @@ class Viewer(wx.Panel): self.text = vtku.TextZero() self.text.SetValue("") self.text.SetPosition(const.TEXT_POS_LEFT_UP) + if sys.platform == 'darwin': + font_size = const.TEXT_SIZE_LARGE * self.GetContentScaleFactor() + self.text.SetSize(int(round(font_size, 0))) self.ren.AddActor(self.text.actor) # self.polygon = Polygon(None, is_3d=False) @@ -318,6 +321,27 @@ class Viewer(wx.Panel): Publisher.subscribe(self.ActivateRobotMode, 'Robot navigation mode') Publisher.subscribe(self.OnUpdateRobotStatus, 'Update robot status') + def get_vtk_mouse_position(self): + """ + Get Mouse position inside a wxVTKRenderWindowInteractorself. Return a + tuple with X and Y position. + Please use this instead of using iren.GetEventPosition because it's + not returning the correct values on Mac with HighDPI display, maybe + the same is happing with Windows and Linux, we need to test. + """ + mposx, mposy = wx.GetMousePosition() + cposx, cposy = self.interactor.ScreenToClient((mposx, mposy)) + mx, my = cposx, self.interactor.GetSize()[1] - cposy + if sys.platform == 'darwin': + # It's needed to mutiple by scale factor in HighDPI because of + # https://docs.wxpython.org/wx.glcanvas.GLCanvas.html + # For now we are doing this only on Mac but it may be needed on + # Windows and Linux too. + scale = self.interactor.GetContentScaleFactor() + mx *= scale + my *= scale + return int(mx), int(my) + def SetStereoMode(self, mode): ren_win = self.interactor.GetRenderWindow() diff --git a/invesalius/data/vtk_utils.py b/invesalius/data/vtk_utils.py index 34619e3..a942ce6 100644 --- a/invesalius/data/vtk_utils.py +++ b/invesalius/data/vtk_utils.py @@ -233,6 +233,7 @@ class TextZero(object): def SetSize(self, size): self.property.SetFontSize(size) + self.actor.GetTextProperty().ShallowCopy(self.property) def SetSymbolicSize(self, size): self.symbolic_syze = size @@ -288,8 +289,8 @@ class TextZero(object): coord.SetValue(*self.position) x, y = coord.GetComputedDisplayValue(canvas.evt_renderer) font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) - # font.SetWeight(wx.FONTWEIGHT_BOLD) font.SetSymbolicSize(self.symbolic_syze) + font.Scale(canvas.viewer.GetContentScaleFactor()) if self.bottom_pos or self.right_pos: w, h = canvas.calc_text_size(self.text, font) if self.right_pos: diff --git a/invesalius/gui/widgets/canvas_renderer.py b/invesalius/gui/widgets/canvas_renderer.py index 948f337..86189d0 100644 --- a/invesalius/gui/widgets/canvas_renderer.py +++ b/invesalius/gui/widgets/canvas_renderer.py @@ -180,13 +180,11 @@ class CanvasRendererCTX: return False def Refresh(self): - print('Refresh') self.modified = True self.viewer.interactor.Render() def OnMouseMove(self, evt): - x, y = evt.GetPosition() - y = self.viewer.interactor.GetSize()[1] - y + x, y = self.viewer.get_vtk_mouse_position() redraw = False if self._drag_obj: @@ -230,8 +228,7 @@ class CanvasRendererCTX: evt.Skip() def OnLeftButtonPress(self, evt): - x, y = evt.GetPosition() - y = self.viewer.interactor.GetSize()[1] - y + x, y = self.viewer.get_vtk_mouse_position() if self._over_obj and hasattr(self._over_obj, 'on_mouse_move'): if hasattr(self._over_obj, 'on_select'): try: @@ -286,8 +283,7 @@ class CanvasRendererCTX: evt.Skip() def OnDoubleClick(self, evt): - x, y = evt.GetPosition() - y = self.viewer.interactor.GetSize()[1] - y + x, y = self.viewer.get_vtk_mouse_position() evt_obj = CanvasEvent('double_left_click', None, (x, y), self.viewer, self.evt_renderer, control_down=evt.ControlDown(), alt_down=evt.AltDown(), @@ -301,6 +297,7 @@ class CanvasRendererCTX: def OnPaint(self, evt, obj): size = self.canvas_renderer.GetSize() w, h = size + ew, eh = self.evt_renderer.GetSize() if self._size != size: self._size = size self._resize_canvas(w, h) @@ -628,17 +625,18 @@ class CanvasRendererCTX: if font is None: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.Scale(self.viewer.GetContentScaleFactor()) - font = gc.CreateFont(font, txt_colour) + _font = gc.CreateFont(font, txt_colour) px, py = pos for t in text.split('\n'): t = t.strip() _py = -py _px = px - gc.SetFont(font) + gc.SetFont(_font) gc.DrawText(t, _px, _py) - w, h = self.calc_text_size(t) + w, h = self.calc_text_size(t, font) py -= h self._drawn = True @@ -661,10 +659,11 @@ class CanvasRendererCTX: if font is None: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.Scale(self.viewer.GetContentScaleFactor()) _font = gc.CreateFont(font, txt_colour) gc.SetFont(_font) - w, h = self.calc_text_size(text) + w, h = self.calc_text_size(text, font) px, py = pos @@ -880,11 +879,13 @@ class CircleHandler(CanvasHandlerBase): def draw_to_canvas(self, gc, canvas): if self.visible: + viewer = canvas.viewer + scale = viewer.GetContentScaleFactor() if self.is_3d: px, py = self._3d_to_2d(canvas.evt_renderer, self.position) else: px, py = self.position - x, y, w, h = canvas.draw_circle((px, py), self.radius, + x, y, w, h = canvas.draw_circle((px, py), self.radius * scale, line_colour=self.line_colour, fill_colour=self.fill_colour) self.bbox = (x - w/2, y - h/2, x + w/2, y + h/2) -- libgit2 0.21.2