From ff1602ef6b951ab02ffad9bffb253d744e9437ec Mon Sep 17 00:00:00 2001 From: tetektoza Date: Thu, 8 May 2025 14:30:31 +0200 Subject: [PATCH] Draft: Introduce new dynamic polygon tracker for Polygon tool (#21045) As the title says - currently we just have a simple circle, so I thought with some basic adjustments it's possible to add a cool tracker that will possibly guide user better on what they are placing on the viewport. --- src/Mod/Draft/draftguitools/gui_polygons.py | 28 ++++---- src/Mod/Draft/draftguitools/gui_trackers.py | 71 +++++++++++++++++++++ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_polygons.py b/src/Mod/Draft/draftguitools/gui_polygons.py index 8825002b41..175255d0d0 100644 --- a/src/Mod/Draft/draftguitools/gui_polygons.py +++ b/src/Mod/Draft/draftguitools/gui_polygons.py @@ -57,7 +57,7 @@ class Polygon(gui_base_original.Creator): 'MenuText': QT_TRANSLATE_NOOP("Draft_Polygon", "Polygon"), 'ToolTip': QT_TRANSLATE_NOOP("Draft_Polygon", "Creates a regular polygon (triangle, square, pentagon, ...), by defining the number of sides and the circumscribed radius.\nCTRL to snap, SHIFT to constrain")} - def Activated(self): + def Activated(self, numVertices=None): """Execute when the command is called.""" super().Activated(name="Polygon") if self.ui: @@ -72,8 +72,10 @@ class Polygon(gui_base_original.Creator): self.ui.numFaces.show() self.ui.numFacesLabel.show() self.altdown = False + self.numVertices = numVertices if numVertices is not None else 3 + self.ui.numFaces.setValue(self.numVertices) self.ui.sourceCmd = self - self.arctrack = trackers.arcTracker() + self.polygonTrack = trackers.polygonTracker(sides=self.numVertices) self.call = self.view.addEventCallback("SoEvent", self.action) _toolmsg(translate("draft", "Pick center point")) @@ -88,10 +90,10 @@ class Polygon(gui_base_original.Creator): """ self.end_callbacks(self.call) if self.ui: - self.arctrack.finalize() + self.polygonTrack.finalize() super().finish() if cont or (cont is None and self.ui and self.ui.continueMode): - self.Activated() + self.Activated(self.numVertices) def action(self, arg): """Handle the 3D scene events. @@ -105,12 +107,15 @@ class Polygon(gui_base_original.Creator): from the 3D view. """ import DraftGeomUtils - + if self.ui.numFaces.value() != self.numVertices: + self.polygonTrack.setNumVertices(self.ui.numFaces.value()) + self.numVertices = self.ui.numFaces.value() if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": # mouse movement detection self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg) + self.polygonTrack.update(ctrlPoint) # this is to make sure radius is what you see on screen if self.center and DraftVecUtils.dist(self.point, self.center) > 0: @@ -134,14 +139,12 @@ class Polygon(gui_base_original.Creator): self.point) _c = DraftGeomUtils.findClosestCircle(self.point, cir) self.center = _c.Center - self.arctrack.setCenter(self.center) elif self.tangents and self.tanpoints: cir = DraftGeomUtils.circleFrom1tan2pt(self.tangents[0], self.tanpoints[0], self.point) _c = DraftGeomUtils.findClosestCircle(self.point, cir) self.center = _c.Center - self.arctrack.setCenter(self.center) if gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()): if not self.altdown: self.altdown = True @@ -158,7 +161,6 @@ class Polygon(gui_base_original.Creator): cl = DraftGeomUtils.findClosestCircle(self.point, cir) self.center = cl.Center self.rad = cl.Radius - self.arctrack.setCenter(self.center) else: self.rad = self.center.add(DraftGeomUtils.findDistance(self.center,ed).sub(self.center)).Length else: @@ -168,7 +170,6 @@ class Polygon(gui_base_original.Creator): self.altdown = False self.rad = DraftVecUtils.dist(self.point, self.center) self.ui.setRadiusValue(self.rad, 'Length') - self.arctrack.setRadius(self.rad) gui_tool_utils.redraw3DView() @@ -190,7 +191,7 @@ class Polygon(gui_base_original.Creator): ed = ob.Shape.Edges[num] self.tangents.append(ed) if len(self.tangents) == 2: - self.arctrack.on() + self.polygonTrack.on() self.ui.radiusUi() self.step = 1 _toolmsg(translate("draft", "Pick radius")) @@ -200,8 +201,8 @@ class Polygon(gui_base_original.Creator): else: self.center = self.point self.node = [self.point] - self.arctrack.setCenter(self.center) - self.arctrack.on() + self.polygonTrack.setOrigin(self.center) + self.polygonTrack.on() self.ui.radiusUi() self.step = 1 _toolmsg(translate("draft", "Pick radius")) @@ -260,8 +261,7 @@ class Polygon(gui_base_original.Creator): """ self.center = App.Vector(numx, numy, numz) self.node = [self.center] - self.arctrack.setCenter(self.center) - self.arctrack.on() + self.polygonTrack.on() self.ui.radiusUi() self.step = 1 self.ui.radiusValue.setFocus() diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 1c067781f6..5664832956 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -249,7 +249,78 @@ class lineTracker(Tracker): p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length + +class polygonTracker(Tracker): + """A Polygon tracker, used by the polygon tool.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, face=False, sides=None): + self.origin = Vector(0, 0, 0) + self.line = coin.SoLineSet() + self.sides = sides if sides is not None else 3 + self.base_angle = None + self.line.numVertices.setValue(self.sides + 1) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 50, [[0, 0, 0], + [2, 0, 0], + [1, 2, 0], + [0, 0, 0]]) + if face: + m1 = coin.SoMaterial() + m1.transparency.setValue(0.5) + m1.diffuseColor.setValue([0.5, 0.5, 1.0]) + f = coin.SoIndexedFaceSet() + f.coordIndex.setValues([0, 1, 2, 3]) + super().__init__(dotted, scolor, swidth, + [self.coords, self.line, m1, f], + name="polygonTracker") + else: + super().__init__(dotted, scolor, swidth, + [self.coords, self.line], + name="polygonTracker") + + def setNumVertices(self, num): + self.line.numVertices.setValue(num + 1) + self.sides = num + + def _drawPolygon(self, local_origin, radius): + wp = self._get_wp() + for i in range(self.sides): + angle = 2 * math.pi * i / self.sides + px = local_origin.x + radius * math.cos(angle) + py = local_origin.y + radius * math.sin(angle) + + # Back to global space + global_point = wp.get_global_coords(Vector(px, py, 0)) + self.coords.point.set1Value(i, *global_point) + + # Close the polygon by repeating the first point + first = self.coords.point[0].getValue() + self.coords.point.set1Value(self.sides, *first) + + def setOrigin(self, point, radius=1.0): + """Set the origin of the polygon and initialize the shape.""" + self.origin = point + wp = self._get_wp() + + # Convert the origin to working plane local space + local_origin = wp.get_local_coords(point) + + self._drawPolygon(local_origin, radius) + + def update(self, point): + """Update polygon size based on current mouse point.""" + wp = self._get_wp() + + # Convert current point and origin to local working plane coordinates + local = wp.get_local_coords(point) + local_origin = wp.get_local_coords(self.origin) + + dx = local.x - local_origin.x + dy = local.y - local_origin.y + radius = math.hypot(dx, dy) + + self._drawPolygon(local_origin, radius) class rectangleTracker(Tracker): """A Rectangle tracker, used by the rectangle tool."""