| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Provides GUI tools to create circular arc objects.""" |
| |
| |
| |
|
|
| |
| |
| import math |
| from PySide.QtCore import QT_TRANSLATE_NOOP |
|
|
| import FreeCAD as App |
| import FreeCADGui as Gui |
| import Draft |
| import DraftVecUtils |
| from FreeCAD import Units as U |
| from draftguitools import gui_base |
| from draftguitools import gui_base_original |
| from draftguitools import gui_tool_utils |
| from draftguitools import gui_trackers as trackers |
| from draftutils import gui_utils |
| from draftutils import params |
| from draftutils import utils |
| from draftutils.messages import _err, _toolmsg |
| from draftutils.translate import translate |
|
|
|
|
| class Arc(gui_base_original.Creator): |
| """Gui command for the Circular Arc tool.""" |
|
|
| def __init__(self): |
| super().__init__() |
| self.closedCircle = False |
| self.featureName = "Arc" |
|
|
| def GetResources(self): |
| """Set icon, menu and tooltip.""" |
| return { |
| "Pixmap": "Draft_Arc", |
| "Accel": "A, R", |
| "MenuText": QT_TRANSLATE_NOOP("Draft_Arc", "Arc"), |
| "ToolTip": QT_TRANSLATE_NOOP( |
| "Draft_Arc", "Creates a circular arc from a center point and a radius" |
| ), |
| } |
|
|
| def Activated(self): |
| """Execute when the command is called.""" |
| super().Activated(name=self.featureName) |
| if self.ui: |
| self.step = 0 |
| self.center = None |
| self.rad = None |
| self.angle = 0 |
| self.tangents = [] |
| self.tanpoints = [] |
| if self.featureName == "Arc": |
| self.ui.arcUi() |
| else: |
| self.ui.circleUi() |
| self.altdown = False |
| self.ui.sourceCmd = self |
| self.linetrack = trackers.lineTracker(dotted=True) |
| self.arctrack = trackers.arcTracker() |
| self.call = self.view.addEventCallback("SoEvent", self.action) |
| _toolmsg(translate("draft", "Pick center point")) |
|
|
| def finish(self, cont=False): |
| """Terminate the operation. |
| |
| Parameters |
| ---------- |
| cont: bool or None, optional |
| Restart (continue) the command if `True`, or if `None` and |
| `ui.continueMode` is `True`. |
| """ |
| self.end_callbacks(self.call) |
| if self.ui: |
| self.linetrack.finalize() |
| self.arctrack.finalize() |
| super().finish() |
| if cont or (cont is None and self.ui and self.ui.continueMode): |
| self.Activated() |
|
|
| def updateAngle(self, angle): |
| """Update the angle with the new value.""" |
| |
| lastangle = self.firstangle + self.angle |
| if lastangle <= -2 * math.pi: |
| lastangle += 2 * math.pi |
| if lastangle >= 2 * math.pi: |
| lastangle -= 2 * math.pi |
| |
| d0 = angle - lastangle |
| d1 = d0 + 2 * math.pi |
| d2 = d0 - 2 * math.pi |
| if abs(d0) < min(abs(d1), abs(d2)): |
| delta = d0 |
| elif abs(d1) < abs(d2): |
| delta = d1 |
| else: |
| delta = d2 |
| newangle = self.angle + delta |
| |
| if newangle >= 2 * math.pi: |
| newangle -= 2 * math.pi |
| if newangle <= -2 * math.pi: |
| newangle += 2 * math.pi |
| self.angle = newangle |
|
|
| def action(self, arg): |
| """Handle the 3D scene events. |
| |
| This is installed as an EventCallback in the Inventor view. |
| |
| Parameters |
| ---------- |
| arg: dict |
| Dictionary with strings that indicates the type of event received |
| from the 3D view. |
| """ |
| import DraftGeomUtils |
|
|
| if arg["Type"] == "SoKeyboardEvent": |
| if arg["Key"] == "ESCAPE": |
| self.finish() |
| elif not self.ui.mouse: |
| pass |
| elif arg["Type"] == "SoLocation2Event": |
| self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg) |
| |
| if self.center and DraftVecUtils.dist(self.point, self.center) > 0: |
| viewdelta = DraftVecUtils.project(self.point.sub(self.center), self.wp.axis) |
| if not DraftVecUtils.isNull(viewdelta): |
| self.point = self.point.add(viewdelta.negative()) |
| if self.step == 0: |
| if gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()): |
| if not self.altdown: |
| self.altdown = True |
| self.ui.switchUi(True) |
| else: |
| if self.altdown: |
| self.altdown = False |
| self.ui.switchUi(False) |
| elif self.step == 1: |
| if len(self.tangents) == 2: |
| cir = DraftGeomUtils.circleFrom2tan1pt( |
| self.tangents[0], self.tangents[1], 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 |
| if info: |
| ob = self.doc.getObject(info["Object"]) |
| num = int(info["Component"].lstrip("Edge")) - 1 |
| ed = ob.Shape.Edges[num] |
| if len(self.tangents) == 2: |
| cir = DraftGeomUtils.circleFrom3tan( |
| self.tangents[0], self.tangents[1], ed |
| ) |
| 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: |
| self.rad = DraftVecUtils.dist(self.point, self.center) |
| else: |
| if self.altdown: |
| self.altdown = False |
| self.rad = DraftVecUtils.dist(self.point, self.center) |
| self.ui.setRadiusValue(self.rad, "Length") |
| self.arctrack.setRadius(self.rad) |
| self.linetrack.p1(self.center) |
| self.linetrack.p2(self.point) |
| self.linetrack.on() |
| elif self.step == 2: |
| currentrad = DraftVecUtils.dist(self.point, self.center) |
| if currentrad != 0: |
| angle = DraftVecUtils.angle( |
| self.wp.u, self.point.sub(self.center), self.wp.axis |
| ) |
| else: |
| angle = 0 |
| self.linetrack.p2( |
| DraftVecUtils.scaleTo(self.point.sub(self.center), self.rad).add(self.center) |
| ) |
| self.ui.setRadiusValue(math.degrees(angle), unit="Angle") |
| self.firstangle = angle |
| else: |
| |
| currentrad = DraftVecUtils.dist(self.point, self.center) |
| if currentrad != 0: |
| angle = DraftVecUtils.angle( |
| self.wp.u, self.point.sub(self.center), self.wp.axis |
| ) |
| else: |
| angle = 0 |
| self.linetrack.p2( |
| DraftVecUtils.scaleTo(self.point.sub(self.center), self.rad).add(self.center) |
| ) |
| self.updateAngle(angle) |
| self.ui.setRadiusValue(math.degrees(self.angle), unit="Angle") |
| self.arctrack.setApertureAngle(self.angle) |
|
|
| gui_tool_utils.redraw3DView() |
|
|
| elif arg["Type"] == "SoMouseButtonEvent": |
| if arg["State"] == "DOWN" and arg["Button"] == "BUTTON1": |
| if self.point: |
| if self.step == 0: |
| if not self.support: |
| gui_tool_utils.getSupport(arg) |
| (self.point, ctrlPoint, info) = gui_tool_utils.getPoint(self, arg) |
| if gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()): |
| snapped = self.view.getObjectInfo( |
| (arg["Position"][0], arg["Position"][1]) |
| ) |
| if snapped: |
| ob = self.doc.getObject(snapped["Object"]) |
| num = int(snapped["Component"].lstrip("Edge")) - 1 |
| ed = ob.Shape.Edges[num] |
| self.tangents.append(ed) |
| if len(self.tangents) == 2: |
| self.arctrack.on() |
| self.ui.radiusUi() |
| self.step = 1 |
| self.ui.setNextFocus() |
| self.linetrack.on() |
| _toolmsg(translate("draft", "Pick radius")) |
| else: |
| if len(self.tangents) == 1: |
| self.tanpoints.append(self.point) |
| else: |
| self.center = self.point |
| self.node = [self.point] |
| self.arctrack.setCenter(self.center) |
| self.linetrack.p1(self.center) |
| self.linetrack.p2( |
| self.view.getPoint(arg["Position"][0], arg["Position"][1]) |
| ) |
| self.arctrack.on() |
| self.ui.radiusUi() |
| self.step = 1 |
| self.ui.setNextFocus() |
| self.linetrack.on() |
| _toolmsg(translate("draft", "Pick radius")) |
| if self.planetrack: |
| self.planetrack.set(self.point) |
| elif self.step == 1: |
| if self.closedCircle: |
| self.drawArc() |
| else: |
| self.ui.labelRadius.setText(translate("draft", "Start angle")) |
| self.ui.radiusValue.setToolTip(translate("draft", "Start angle")) |
| self.ui.radiusValue.setText(U.Quantity(0, U.Angle).UserString) |
| self.linetrack.p1(self.center) |
| self.linetrack.on() |
| self.step = 2 |
| _toolmsg(translate("draft", "Pick start angle")) |
| elif self.step == 2: |
| self.ui.labelRadius.setText(translate("draft", "Aperture angle")) |
| self.ui.radiusValue.setToolTip(translate("draft", "Aperture angle")) |
| ang_offset = DraftVecUtils.angle( |
| self.wp.u, self.arctrack.getDeviation(), self.wp.axis |
| ) |
| self.arctrack.setStartAngle(self.firstangle - ang_offset) |
| self.step = 3 |
| _toolmsg(translate("draft", "Pick aperture")) |
| else: |
| self.step = 4 |
| self.drawArc() |
| self.update_hints() |
|
|
| def drawArc(self): |
| """Actually draw the arc object.""" |
| rot, sup, pts, fil = self.getStrings() |
| if self.closedCircle: |
| try: |
| |
| |
| Gui.addModule("Draft") |
| if params.get_param("UsePartPrimitives"): |
| |
| _base = DraftVecUtils.toString(self.center) |
| _cmd = "FreeCAD.ActiveDocument." |
| _cmd += 'addObject("Part::Circle", "Circle")' |
| _cmd_list = [ |
| "circle = " + _cmd, |
| "circle.Radius = " + str(self.rad), |
| "pl = FreeCAD.Placement()", |
| "pl.Rotation.Q = " + rot, |
| "pl.Base = " + _base, |
| "circle.Placement = pl", |
| "Draft.autogroup(circle)", |
| "Draft.select(circle)", |
| "FreeCAD.ActiveDocument.recompute()", |
| ] |
| self.commit(translate("draft", "Create Circle (Part)"), _cmd_list) |
| else: |
| |
| _base = DraftVecUtils.toString(self.center) |
| _cmd = "Draft.make_circle" |
| _cmd += "(" |
| _cmd += "radius=" + str(self.rad) + ", " |
| _cmd += "placement=pl, " |
| _cmd += "face=" + fil + ", " |
| _cmd += "support=" + sup |
| _cmd += ")" |
| _cmd_list = [ |
| "pl=FreeCAD.Placement()", |
| "pl.Rotation.Q=" + rot, |
| "pl.Base=" + _base, |
| "circle = " + _cmd, |
| "Draft.autogroup(circle)", |
| "FreeCAD.ActiveDocument.recompute()", |
| ] |
| self.commit(translate("draft", "Create Circle"), _cmd_list) |
| except Exception: |
| _err("Draft: error delaying commit") |
| else: |
| |
| sta = math.degrees(self.firstangle) |
| end = math.degrees(self.firstangle + self.angle) |
| if end < sta: |
| sta, end = end, sta |
| sta %= 360 |
| end %= 360 |
|
|
| try: |
| Gui.addModule("Draft") |
| if params.get_param("UsePartPrimitives"): |
| |
| _base = DraftVecUtils.toString(self.center) |
| _cmd = "FreeCAD.ActiveDocument." |
| _cmd += 'addObject("Part::Circle", "Circle")' |
| _cmd_list = [ |
| "circle = " + _cmd, |
| "circle.Radius = " + str(self.rad), |
| "circle.Angle1 = " + str(sta), |
| "circle.Angle2 = " + str(end), |
| "pl = FreeCAD.Placement()", |
| "pl.Rotation.Q = " + rot, |
| "pl.Base = " + _base, |
| "circle.Placement = pl", |
| "Draft.autogroup(circle)", |
| "Draft.select(circle)", |
| "FreeCAD.ActiveDocument.recompute()", |
| ] |
| self.commit(translate("draft", "Create Arc (Part)"), _cmd_list) |
| else: |
| |
| _base = DraftVecUtils.toString(self.center) |
| _cmd = "Draft.make_circle" |
| _cmd += "(" |
| _cmd += "radius=" + str(self.rad) + ", " |
| _cmd += "placement=pl, " |
| _cmd += "face=" + fil + ", " |
| _cmd += "startangle=" + str(sta) + ", " |
| _cmd += "endangle=" + str(end) + ", " |
| _cmd += "support=" + sup |
| _cmd += ")" |
| _cmd_list = [ |
| "pl = FreeCAD.Placement()", |
| "pl.Rotation.Q = " + rot, |
| "pl.Base = " + _base, |
| "circle = " + _cmd, |
| "Draft.autogroup(circle)", |
| "FreeCAD.ActiveDocument.recompute()", |
| ] |
| self.commit(translate("draft", "Create Arc"), _cmd_list) |
| except Exception: |
| _err("Draft: error delaying commit") |
|
|
| |
| self.finish(cont=None) |
|
|
| def numericInput(self, numx, numy, numz): |
| """Validate the entry fields in the user interface. |
| |
| This function is called by the toolbar or taskpanel interface |
| when valid x, y, and z have been entered in the input fields. |
| """ |
| self.center = App.Vector(numx, numy, numz) |
| self.node = [self.center] |
| self.arctrack.setCenter(self.center) |
| self.arctrack.on() |
| self.ui.radiusUi() |
| self.step = 1 |
| self.ui.setNextFocus() |
| _toolmsg(translate("draft", "Pick radius")) |
| self.update_hints() |
|
|
| def numericRadius(self, rad): |
| """Validate the entry radius in the user interface. |
| |
| This function is called by the toolbar or taskpanel interface |
| when a valid radius has been entered in the input field. |
| """ |
| import DraftGeomUtils |
|
|
| if self.step == 1: |
| self.rad = rad |
| if len(self.tangents) == 2: |
| cir = DraftGeomUtils.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad) |
| if self.center: |
| _c = DraftGeomUtils.findClosestCircle(self.center, cir) |
| self.center = _c.Center |
| else: |
| self.center = cir[-1].Center |
| elif self.tangents and self.tanpoints: |
| cir = DraftGeomUtils.circleFrom1tan1pt1rad(self.tangents[0], self.tanpoints[0], rad) |
| if self.center: |
| _c = DraftGeomUtils.findClosestCircle(self.center, cir) |
| self.center = _c.Center |
| else: |
| self.center = cir[-1].Center |
| if self.closedCircle: |
| self.drawArc() |
| else: |
| self.step = 2 |
| self.arctrack.setCenter(self.center) |
| self.ui.labelRadius.setText(translate("draft", "Start angle")) |
| self.ui.radiusValue.setToolTip(translate("draft", "Start angle")) |
| self.linetrack.p1(self.center) |
| self.linetrack.on() |
| self.ui.radiusValue.setText("") |
| self.ui.radiusValue.setFocus() |
| _toolmsg(translate("draft", "Pick start angle")) |
| elif self.step == 2: |
| self.ui.labelRadius.setText(translate("draft", "Aperture angle")) |
| self.ui.radiusValue.setToolTip(translate("draft", "Aperture angle")) |
| self.firstangle = math.radians(rad) |
| ang_offset = DraftVecUtils.angle(self.wp.u, self.arctrack.getDeviation(), self.wp.axis) |
| self.arctrack.setStartAngle(self.firstangle - ang_offset) |
| self.step = 3 |
| self.ui.radiusValue.setText("") |
| self.ui.radiusValue.setFocus() |
| _toolmsg(translate("draft", "Pick aperture angle")) |
| else: |
| self.updateAngle(rad) |
| self.angle = math.radians(rad) |
| self.step = 4 |
| self.drawArc() |
| self.update_hints() |
|
|
| def get_hints(self): |
| if self.step == 0: |
| hints = [Gui.InputHint(translate("draft", "%1 pick center"), Gui.UserInput.MouseLeft)] |
| elif self.step == 1: |
| hints = [Gui.InputHint(translate("draft", "%1 pick radius"), Gui.UserInput.MouseLeft)] |
| elif self.step == 2: |
| hints = [ |
| Gui.InputHint(translate("draft", "%1 pick start angle"), Gui.UserInput.MouseLeft) |
| ] |
| else: |
| hints = [Gui.InputHint(translate("draft", "%1 pick aperture"), Gui.UserInput.MouseLeft)] |
| return ( |
| hints |
| + gui_tool_utils._get_hint_xyz_constrain() |
| + gui_tool_utils._get_hint_mod_constrain() |
| + gui_tool_utils._get_hint_mod_snap() |
| ) |
|
|
|
|
| Gui.addCommand("Draft_Arc", Arc()) |
|
|
|
|
| class Arc_3Points(gui_base.GuiCommandBase): |
| """GuiCommand for the Draft_Arc_3Points tool.""" |
|
|
| def __init__(self): |
| super().__init__(name="Arc_3Points") |
|
|
| def GetResources(self): |
| """Set icon, menu and tooltip.""" |
| return { |
| "Pixmap": "Draft_Arc_3Points", |
| "Accel": "A, T", |
| "MenuText": QT_TRANSLATE_NOOP("Draft_Arc_3Points", "Arc From 3 Points"), |
| "ToolTip": QT_TRANSLATE_NOOP( |
| "Draft_Arc_3Points", "Creates a circular arc from 3 points" |
| ), |
| } |
|
|
| def Activated(self): |
| """Execute when the command is called.""" |
| super().Activated() |
|
|
| |
| self.points = [] |
| self.normal = None |
| self.tracker = trackers.arcTracker() |
| self.tracker.autoinvert = False |
|
|
| |
| |
| |
| import WorkingPlane |
|
|
| WorkingPlane.get_working_plane() |
|
|
| Gui.Snapper.getPoint(callback=self.getPoint, movecallback=self.drawArc) |
| Gui.Snapper.ui.sourceCmd = self |
| Gui.Snapper.ui.setTitle( |
| title=translate("draft", "Arc From 3 Points"), icon="Draft_Arc_3Points" |
| ) |
| Gui.Snapper.ui.continueCmd.show() |
| self.update_hints() |
|
|
| def getPoint(self, point, info): |
| """Get the point by clicking on the 3D view. |
| |
| Every time the user clicks on the 3D view this method is run. |
| In this case, a point is appended to the list of points, |
| and the tracker is updated. |
| The object is finally created when three points are picked. |
| |
| Parameters |
| ---------- |
| point: Base::Vector |
| The point selected in the 3D view. |
| |
| info: str |
| Some information obtained about the point passed by the Snapper. |
| """ |
| |
| |
| if not point: |
| return None |
|
|
| |
| if point not in self.points: |
| self.points.append(point) |
| if self.planetrack and len(self.points) == 1: |
| self.planetrack.set(point) |
|
|
| if len(self.points) < 3: |
| |
| |
| |
| |
| |
| |
| if len(self.points) == 2: |
| self.tracker.on() |
| Gui.Snapper.getPoint( |
| last=self.points[-1], callback=self.getPoint, movecallback=self.drawArc |
| ) |
| Gui.Snapper.ui.sourceCmd = self |
| Gui.Snapper.ui.setTitle( |
| title=translate("draft", "Arc From 3 Points"), icon="Draft_Arc_3Points" |
| ) |
| Gui.Snapper.ui.continueCmd.show() |
| self.update_hints() |
|
|
| else: |
| |
| |
| |
| Gui.addModule("Draft") |
| Gui.addModule("WorkingPlane") |
| _cmd = "Draft.make_arc_3points([" |
| _cmd += "FreeCAD." + str(self.points[0]) |
| _cmd += ", FreeCAD." + str(self.points[1]) |
| _cmd += ", FreeCAD." + str(self.points[2]) |
| _cmd += "]" |
| _cmd += ", placement=pl" |
| _cmd += ", primitive=" + str(params.get_param("UsePartPrimitives")) |
| _cmd += ")" |
| _cmd_list = [ |
| "pl = WorkingPlane.get_working_plane().get_placement()", |
| "circle = " + _cmd, |
| "Draft.autogroup(circle)", |
| ] |
| if params.get_param("UsePartPrimitives"): |
| _cmd_list.append("Draft.select(circle)") |
| _cmd_list.append("FreeCAD.ActiveDocument.recompute()") |
| self.commit(translate("draft", "Create Arc From 3 Points"), _cmd_list) |
| self.finish(cont=None) |
|
|
| def drawArc(self, point, info): |
| """Draw preview arc when we move the pointer in the 3D view. |
| |
| It uses the `gui_trackers.arcTracker.setBy3Points` method. |
| |
| Parameters |
| ---------- |
| point: Base::Vector |
| The dynamic point passed by the callback |
| as we move the pointer in the 3D view. |
| |
| info: str |
| Some information obtained from the point by the Snapper. |
| """ |
| if len(self.points) == 2: |
| if point.sub(self.points[1]).Length > 0.001: |
| self.tracker.setBy3Points(self.points[0], self.points[1], point) |
|
|
| def finish(self, cont=False): |
| """Terminate the operation. |
| |
| Parameters |
| ---------- |
| cont: bool or None, optional |
| Restart (continue) the command if `True`, or if `None` and |
| `ui.continueMode` is `True`. |
| """ |
| self.tracker.finalize() |
| super().finish() |
| if cont or (cont is None and Gui.Snapper.ui and Gui.Snapper.ui.continueMode): |
| self.Activated() |
|
|
| def get_hints(self): |
| if len(self.points) == 0: |
| hints = [ |
| Gui.InputHint(translate("draft", "%1 pick first point"), Gui.UserInput.MouseLeft) |
| ] |
| elif len(self.points) == 1: |
| hints = [ |
| Gui.InputHint(translate("draft", "%1 pick second point"), Gui.UserInput.MouseLeft) |
| ] |
| else: |
| hints = [ |
| Gui.InputHint(translate("draft", "%1 pick third point"), Gui.UserInput.MouseLeft) |
| ] |
| return ( |
| hints |
| + gui_tool_utils._get_hint_xyz_constrain() |
| + gui_tool_utils._get_hint_mod_constrain() |
| + gui_tool_utils._get_hint_mod_snap() |
| ) |
|
|
|
|
| Draft_Arc_3Points = Arc_3Points |
| Gui.addCommand("Draft_Arc_3Points", Arc_3Points()) |
|
|
|
|
| class ArcGroup: |
| """Gui Command group for the Arc tools.""" |
|
|
| def GetResources(self): |
| """Set icon, menu and tooltip.""" |
| return { |
| "MenuText": QT_TRANSLATE_NOOP("Draft_ArcTools", "Arc Tools"), |
| "ToolTip": QT_TRANSLATE_NOOP( |
| "Draft_ArcTools", "Tools to create various types of circular arcs" |
| ), |
| } |
|
|
| def GetCommands(self): |
| """Return a tuple of commands in the group.""" |
| return ("Draft_Arc", "Draft_Arc_3Points") |
|
|
| def IsActive(self): |
| """Return True when this command should be available.""" |
| return bool(gui_utils.get_3d_view()) |
|
|
|
|
| Gui.addCommand("Draft_ArcTools", ArcGroup()) |
|
|
| |
|
|