Download src/Mod/Draft/draftguitools/gui_edit_draft_objects.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 36.9 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py
-
curl -L -o gui_edit_draft_objects.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py
36.9 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2010 Ken Cline <cline@frii.com> * | |
| # * Copyright (c) 2020 Carlo Pavan <carlopav@gmail.com> * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENCE text file. * | |
| # * * | |
| # * This program is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| """Provides support functions to edit Draft objects. | |
| All functions in this module work with Object coordinate space. | |
| No conversion to global coordinate system is needed. | |
| To support an new Object, Draft_Edit needs at least two functions: | |
| getObjectPts(obj): returns a list of points on which Draft_Edit will display | |
| edit trackers | |
| updateObject(obj, nodeIndex, v): update the give object according to the | |
| index of a moved edit tracker and the vector of the displacement | |
| TODO: Abstract the code that handles the preview and move the object specific | |
| code to this module from main Draft_Edit module | |
| """ | |
| ## @package gui_edit_draft_objects | |
| # \ingroup draftguitools | |
| # \brief Provides support functions to edit Draft objects. | |
| __title__ = "FreeCAD Draft Edit Tool" | |
| __author__ = ( | |
| "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " "Dmitry Chigrin, Carlo Pavan" | |
| ) | |
| __url__ = "https://www.freecad.org" | |
| ## \addtogroup draftguitools | |
| # @{ | |
| import math | |
| import FreeCAD as App | |
| import FreeCADGui as Gui | |
| import DraftVecUtils | |
| from draftutils.translate import translate | |
| import draftutils.utils as utils | |
| from draftutils.messages import _err | |
| import draftguitools.gui_trackers as trackers | |
| from draftguitools.gui_edit_base_object import GuiTools | |
| class DraftWireGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| for p in obj.Points: | |
| editpoints.append(p) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| pts = obj.Points | |
| tol = 0.001 # TODO : Use default precision | |
| if node_idx == 0 and (v - pts[-1]).Length < tol: | |
| # DNC: user moved first point over last point -> Close curve | |
| obj.Closed = True | |
| pts[0] = v | |
| del pts[-1] | |
| obj.Points = pts | |
| return | |
| elif node_idx == len(pts) - 1 and (v - pts[0]).Length < tol: | |
| # DNC: user moved last point over first point -> Close curve | |
| obj.Closed = True | |
| del pts[-1] | |
| obj.Points = pts | |
| return | |
| elif v in pts: | |
| # DNC: checks if point enter is equal to other, this could cause a OCC problem | |
| _err = translate("draft", "This object does not support possible " "coincident points") | |
| App.Console.PrintMessage(_err + "\n") | |
| return | |
| # TODO: Make consistent operation with trackers and open wires | |
| # See: https://forum.freecad.org/viewtopic.php?f=23&t=56661 | |
| # if obj.Closed: | |
| # # DNC: project the new point to the plane of the face if present | |
| # if hasattr(obj.Shape, "normalAt"): | |
| # normal = obj.Shape.normalAt(0,0) | |
| # point_on_plane = obj.Shape.Vertexes[0].Point | |
| # v.projectToPlane(point_on_plane, normal) | |
| pts[node_idx] = v | |
| obj.Points = pts | |
| def get_edit_point_context_menu(self, edit_command, obj, node_idx): | |
| return [ | |
| (translate("draft", "Delete Point"), lambda: self.delete_point(obj, node_idx)), | |
| ] | |
| def get_edit_obj_context_menu(self, edit_command, obj, position): | |
| return [ | |
| (translate("draft", "Add Point"), lambda: self.add_point(edit_command, obj, position)), | |
| (self.get_open_close_menu_text(obj), lambda: self.open_close_wire(obj)), | |
| (self.get_reverse_menu_text(obj), lambda: self.reverse_wire(obj)), | |
| ] | |
| def get_open_close_menu_text(self, obj): | |
| """This function is overridden in the DraftBSplineGuiTools class.""" | |
| if obj.Closed: | |
| return translate("draft", "Open Wire") | |
| else: | |
| return translate("draft", "Close Wire") | |
| def get_reverse_menu_text(self, obj): | |
| """This function is overridden in the DraftBSplineGuiTools class.""" | |
| return translate("draft", "Reverse Wire") | |
| def init_preview_object(self, obj): | |
| return trackers.wireTracker(obj.Shape) | |
| def update_preview_object(self, edit_command, obj, node_idx, v): | |
| if utils.get_type(obj) in ["Wire"]: | |
| pointList = edit_command.globalize_vectors(obj, obj.Points) | |
| pointList[node_idx] = v | |
| if obj.Closed: | |
| pointList.append(pointList[0]) | |
| edit_command.ghost.updateFromPointlist(pointList) | |
| def add_point(self, edit_command, obj, pos): | |
| """Add point to obj.""" | |
| info, newPoint = edit_command.get_specific_object_info(obj, pos) | |
| if not info: | |
| return | |
| if not "Edge" in info["Component"]: | |
| return | |
| edgeIndex = int(info["Component"][4:]) | |
| newPoints = [] | |
| if hasattr(obj, "ChamferSize") and hasattr(obj, "FilletRadius"): | |
| # TODO: If Draft_Wire fails to calculate one of the fillets or chamfers | |
| # this algo fails to identify the correct edge | |
| if obj.ChamferSize > 0 and obj.FilletRadius > 0: | |
| edgeIndex = (edgeIndex + 3) / 4 | |
| elif obj.ChamferSize > 0 or obj.FilletRadius > 0: | |
| edgeIndex = (edgeIndex + 1) / 2 | |
| for index, point in enumerate(obj.Points): | |
| if index == edgeIndex: | |
| newPoints.append(edit_command.localize_vector(obj, newPoint)) | |
| newPoints.append(point) | |
| if obj.Closed and edgeIndex == len(obj.Points): | |
| # last segment when object is closed | |
| newPoints.append(edit_command.localize_vector(obj, newPoint)) | |
| obj.Points = newPoints | |
| obj.recompute() | |
| def delete_point(self, obj, node_idx): | |
| if len(obj.Points) <= 2: | |
| _err(translate("draft", "Active object must have more than 2 points or nodes")) | |
| return | |
| pts = obj.Points | |
| pts.pop(node_idx) | |
| obj.Points = pts | |
| obj.recompute() | |
| def open_close_wire(self, obj): | |
| obj.Closed = not obj.Closed | |
| obj.recompute() | |
| def reverse_wire(self, obj): | |
| obj.Points = reversed(obj.Points) | |
| obj.recompute() | |
| class DraftBSplineGuiTools(DraftWireGuiTools): | |
| def get_open_close_menu_text(self, obj): | |
| if obj.Closed: | |
| return translate("draft", "Open Spline") | |
| else: | |
| return translate("draft", "Close Spline") | |
| def get_reverse_menu_text(self, obj): | |
| return translate("draft", "Reverse Spline") | |
| def init_preview_object(self, obj): | |
| return trackers.bsplineTracker() | |
| def update_preview_object(self, edit_command, obj, node_idx, v): | |
| pointList = edit_command.globalize_vectors(obj, obj.Points) | |
| pointList[node_idx] = v | |
| if obj.Closed: | |
| pointList.append(pointList[0]) | |
| edit_command.ghost.update(pointList) | |
| def add_point(self, edit_command, obj, pos): | |
| """Add point to obj.""" | |
| info, global_pt = edit_command.get_specific_object_info(obj, pos) | |
| pt = edit_command.localize_vector(obj, global_pt) | |
| if not info or (pt is None): | |
| return | |
| pts = obj.Points | |
| if obj.Closed: | |
| curve = obj.Shape.Edges[0].Curve | |
| else: | |
| curve = obj.Shape.Curve | |
| uNewPoint = curve.parameter(obj.Placement.multVec(pt)) | |
| uPoints = curve.getKnots() | |
| for i in range(len(uPoints) - 1): | |
| if (uNewPoint > uPoints[i]) and (uNewPoint < uPoints[i + 1]): | |
| pts.insert(i + 1, pt) | |
| break | |
| # DNC: fix: add points to last segment if curve is closed | |
| if obj.Closed and (uNewPoint > uPoints[-1]): | |
| pts.append(pt) | |
| obj.Points = pts | |
| obj.recompute() | |
| class DraftRectangleGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| """Return the list of edipoints for the given Draft Rectangle. | |
| 0 : Placement.Base | |
| 1 : Length | |
| 2 : Height | |
| """ | |
| editpoints = [] | |
| editpoints.append(App.Vector(0, 0, 0)) | |
| editpoints.append(App.Vector(obj.Length, 0, 0)) | |
| editpoints.append(App.Vector(0, obj.Height, 0)) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if node_idx == 0: | |
| obj.Placement.Base = obj.Placement.multVec(v) | |
| elif node_idx == 1: | |
| obj.Length = DraftVecUtils.project(v, App.Vector(1, 0, 0)).Length | |
| elif node_idx == 2: | |
| obj.Height = DraftVecUtils.project(v, App.Vector(0, 1, 0)).Length | |
| class DraftCircleGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| """Return the list of edipoints for the given Draft Arc or Circle. | |
| circle: | |
| 0 : Placement.Base or center | |
| 1 : radius | |
| arc: | |
| 0 : Placement.Base or center | |
| 1 : first endpoint | |
| 2 : second endpoint | |
| 3 : midpoint | |
| """ | |
| editpoints = [] | |
| editpoints.append(App.Vector(0, 0, 0)) | |
| if obj.FirstAngle == obj.LastAngle: | |
| # obj is a circle | |
| editpoints.append(App.Vector(obj.Radius, 0, 0)) | |
| else: | |
| # obj is an arc | |
| editpoints.append(self.getArcStart(obj)) # First endpoint | |
| editpoints.append(self.getArcEnd(obj)) # Second endpoint | |
| editpoints.append(self.getArcMid(obj)) # Midpoint | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if obj.FirstAngle == obj.LastAngle: | |
| # object is a circle | |
| if node_idx == 0: | |
| obj.Placement.Base = obj.Placement.multVec(v) | |
| elif node_idx == 1: | |
| obj.Radius = v.Length | |
| else: | |
| # obj is an arc | |
| if alt_edit_mode == 0: | |
| import Part | |
| if node_idx == 0: | |
| # center point | |
| p1 = self.getArcStart(obj) | |
| p2 = self.getArcEnd(obj) | |
| p0 = DraftVecUtils.project(v, self.getArcMid(obj)) | |
| obj.Radius = p1.sub(p0).Length | |
| obj.FirstAngle = -math.degrees(DraftVecUtils.angle(p1.sub(p0))) | |
| obj.LastAngle = -math.degrees(DraftVecUtils.angle(p2.sub(p0))) | |
| obj.Placement.Base = obj.Placement.multVec(p0) | |
| else: | |
| """Edit arc by 3 points.""" | |
| v = obj.Placement.multVec(v) | |
| p1 = obj.Placement.multVec(self.getArcStart(obj)) | |
| p2 = obj.Placement.multVec(self.getArcMid(obj)) | |
| p3 = obj.Placement.multVec(self.getArcEnd(obj)) | |
| if node_idx == 1: # first point | |
| p1 = v | |
| elif node_idx == 3: # midpoint | |
| p2 = v | |
| elif node_idx == 2: # second point | |
| p3 = v | |
| arc = Part.ArcOfCircle(p1, p2, p3) | |
| import Part | |
| s = arc.toShape() | |
| # Part.show(s) DEBUG | |
| p0 = arc.Location | |
| obj.Placement.Base = p0 | |
| obj.Radius = arc.Radius | |
| delta = s.Vertexes[0].Point | |
| obj.FirstAngle = -math.degrees(DraftVecUtils.angle(p1.sub(p0))) | |
| delta = s.Vertexes[1].Point | |
| obj.LastAngle = -math.degrees(DraftVecUtils.angle(p3.sub(p0))) | |
| elif alt_edit_mode == 1: | |
| # edit arc by center radius FirstAngle LastAngle | |
| if node_idx == 0: | |
| obj.Placement.Base = obj.Placement.multVec(v) | |
| else: | |
| dangle = math.degrees(math.atan2(v[1], v[0])) | |
| if node_idx == 1: | |
| obj.FirstAngle = dangle | |
| elif node_idx == 2: | |
| obj.LastAngle = dangle | |
| elif node_idx == 3: | |
| obj.Radius = v.Length | |
| def get_edit_point_context_menu(self, edit_command, obj, node_idx): | |
| actions = None | |
| if obj.FirstAngle != obj.LastAngle: | |
| if node_idx == 0: # user is over arc start point | |
| return [ | |
| ( | |
| translate("draft", "Move Arc"), | |
| lambda: self.handle_move_arc(edit_command, obj, node_idx), | |
| ), | |
| ] | |
| elif node_idx == 1: # user is over arc start point | |
| return [ | |
| ( | |
| translate("draft", "Set First Angle"), | |
| lambda: self.handle_set_first_angle(edit_command, obj, node_idx), | |
| ), | |
| ] | |
| elif node_idx == 2: # user is over arc end point | |
| return [ | |
| ( | |
| translate("draft", "Set Last Angle"), | |
| lambda: self.handle_set_last_angle(edit_command, obj, node_idx), | |
| ), | |
| ] | |
| elif node_idx == 3: # user is over arc mid point | |
| return [ | |
| ( | |
| translate("draft", "Set Radius"), | |
| lambda: self.handle_set_radius(edit_command, obj, node_idx), | |
| ), | |
| ] | |
| def handle_move_arc(self, edit_command, obj, node_idx): | |
| edit_command.alt_edit_mode = 1 | |
| edit_command.startEditing(obj, node_idx) | |
| def handle_set_first_angle(self, edit_command, obj, node_idx): | |
| edit_command.alt_edit_mode = 1 | |
| edit_command.startEditing(obj, node_idx) | |
| def handle_set_last_angle(self, edit_command, obj, node_idx): | |
| edit_command.alt_edit_mode = 1 | |
| edit_command.startEditing(obj, node_idx) | |
| def handle_set_radius(self, edit_command, obj, node_idx): | |
| edit_command.alt_edit_mode = 1 | |
| edit_command.startEditing(obj, node_idx) | |
| def get_edit_obj_context_menu(self, edit_command, obj, position): | |
| # Do not show the `Invert arc` option for circles: | |
| if obj.FirstAngle == obj.LastAngle: | |
| return | |
| return [ | |
| (translate("draft", "Invert Arc"), lambda: self.arcInvert(obj)), | |
| ] | |
| def init_preview_object(self, obj): | |
| return trackers.arcTracker() | |
| def update_preview_object(self, edit_command, obj, node_idx, v): | |
| edit_command.ghost.setCenter(obj.getGlobalPlacement().Base) | |
| edit_command.ghost.setRadius(obj.Radius) | |
| if obj.FirstAngle == obj.LastAngle: | |
| # obj is a circle | |
| edit_command.ghost.circle = True | |
| if node_idx == 0: | |
| edit_command.ghost.setCenter(v) | |
| elif node_idx == 1: | |
| radius = v.sub(obj.getGlobalPlacement().Base).Length | |
| edit_command.ghost.setRadius(radius) | |
| else: | |
| if edit_command.alt_edit_mode == 0: | |
| # edit by 3 points | |
| if node_idx == 0: | |
| # center point | |
| p1 = edit_command.localize_vector(obj, obj.Shape.Vertexes[0].Point) | |
| p2 = edit_command.localize_vector(obj, obj.Shape.Vertexes[1].Point) | |
| p0 = DraftVecUtils.project( | |
| edit_command.localize_vector(obj, v), | |
| edit_command.localize_vector( | |
| obj, (self.getArcMid(obj, global_placement=True)) | |
| ), | |
| ) | |
| edit_command.ghost.autoinvert = False | |
| edit_command.ghost.setRadius(p1.sub(p0).Length) | |
| edit_command.ghost.setStartPoint(obj.Shape.Vertexes[1].Point) | |
| edit_command.ghost.setEndPoint(obj.Shape.Vertexes[0].Point) | |
| edit_command.ghost.setCenter(edit_command.globalize_vector(obj, p0)) | |
| return | |
| else: | |
| p1 = self.getArcStart(obj, global_placement=True) | |
| p2 = self.getArcMid(obj, global_placement=True) | |
| p3 = self.getArcEnd(obj, global_placement=True) | |
| if node_idx == 1: | |
| p1 = v | |
| elif node_idx == 3: | |
| p2 = v | |
| elif node_idx == 2: | |
| p3 = v | |
| edit_command.ghost.setBy3Points(p1, p2, p3) | |
| elif edit_command.alt_edit_mode == 1: | |
| # edit by center radius angles | |
| edit_command.ghost.setStartAngle(math.radians(obj.FirstAngle)) | |
| edit_command.ghost.setEndAngle(math.radians(obj.LastAngle)) | |
| if node_idx == 0: | |
| edit_command.ghost.setCenter(v) | |
| elif node_idx == 1: | |
| edit_command.ghost.setStartPoint(v) | |
| elif node_idx == 2: | |
| edit_command.ghost.setEndPoint(v) | |
| elif node_idx == 3: | |
| edit_command.ghost.setRadius(edit_command.localize_vector(obj, v).Length) | |
| def getArcStart(self, obj, global_placement=False): # Returns object midpoint | |
| if utils.get_type(obj) == "Circle": | |
| return self.pointOnCircle(obj, obj.FirstAngle, global_placement) | |
| def getArcEnd(self, obj, global_placement=False): # Returns object midpoint | |
| if utils.get_type(obj) == "Circle": | |
| return self.pointOnCircle(obj, obj.LastAngle, global_placement) | |
| def getArcMid(self, obj, global_placement=False): # Returns object midpoint | |
| if utils.get_type(obj) == "Circle": | |
| if obj.LastAngle > obj.FirstAngle: | |
| midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0 | |
| else: | |
| midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0 | |
| midAngle += App.Units.Quantity(180, App.Units.Angle) | |
| return self.pointOnCircle(obj, midAngle, global_placement) | |
| def pointOnCircle(self, obj, angle, global_placement=False): | |
| if utils.get_type(obj) == "Circle": | |
| px = obj.Radius * math.cos(math.radians(angle)) | |
| py = obj.Radius * math.sin(math.radians(angle)) | |
| p = App.Vector(px, py, 0.0) | |
| if global_placement: | |
| p = obj.getGlobalPlacement().multVec(p) | |
| return p | |
| return None | |
| def arcInvert(self, obj): | |
| obj.FirstAngle, obj.LastAngle = obj.LastAngle, obj.FirstAngle | |
| obj.recompute() | |
| class DraftEllipseGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| editpoints.append(App.Vector(0, 0, 0)) | |
| editpoints.append(App.Vector(obj.MajorRadius, 0, 0)) | |
| editpoints.append(App.Vector(0, obj.MinorRadius, 0)) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if node_idx == 0: | |
| obj.Placement.Base = obj.Placement.multVec(v) | |
| elif node_idx == 1: | |
| if v.Length >= obj.MinorRadius: | |
| obj.MajorRadius = v.Length | |
| else: | |
| obj.MajorRadius = obj.MinorRadius | |
| elif node_idx == 2: | |
| if v.Length <= obj.MajorRadius: | |
| obj.MinorRadius = v.Length | |
| else: | |
| obj.MinorRadius = obj.MajorRadius | |
| class DraftPolygonGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| editpoints.append(App.Vector(0, 0, 0)) | |
| if obj.DrawMode == "inscribed": | |
| editpoints.append(obj.Placement.inverse().multVec(obj.Shape.Vertexes[0].Point)) | |
| else: | |
| editpoints.append( | |
| obj.Placement.inverse().multVec( | |
| (obj.Shape.Vertexes[0].Point + obj.Shape.Vertexes[1].Point) / 2 | |
| ) | |
| ) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if node_idx == 0: | |
| obj.Placement.Base = obj.Placement.multVec(v) | |
| elif node_idx == 1: | |
| obj.Radius = v.Length | |
| obj.recompute() | |
| class DraftDimensionGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| p = obj.ViewObject.Proxy.textpos.translation.getValue() | |
| editpoints.append(obj.Start) | |
| editpoints.append(obj.End) | |
| editpoints.append(obj.Dimline) | |
| editpoints.append(App.Vector(p[0], p[1], p[2])) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if node_idx == 0: | |
| obj.Start = v | |
| elif node_idx == 1: | |
| obj.End = v | |
| elif node_idx == 2: | |
| obj.Dimline = v | |
| elif node_idx == 3: | |
| obj.ViewObject.TextPosition = v | |
| class DraftLabelGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| if obj.StraightDirection != "Custom": | |
| editpoints.append(obj.Points[0]) | |
| editpoints.append(obj.Points[1]) | |
| editpoints.append(obj.Points[2]) | |
| if editpoints[0].isEqual(editpoints[1], 0.001): | |
| # If StraightDistance == 0.0, editpoint 1 is exactly on editpoint 0 => Move slightly editpoint 1 to allow selection of editpoint 0 | |
| editpoints[1] = 0.9 * editpoints[1] + 0.1 * editpoints[2] | |
| else: | |
| pass # TODO : support custom direction with any number of points | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| if obj.StraightDirection != "Custom": | |
| if node_idx == 0: | |
| vector_p1_p2 = obj.Placement.inverse().multVec( | |
| obj.Points[1] | |
| ) - obj.Placement.inverse().multVec(v) | |
| if obj.StraightDirection == "Horizontal": | |
| if round(vector_p1_p2.y, utils.precision()) == 0.0: | |
| obj.StraightDistance = vector_p1_p2.x | |
| elif obj.StraightDirection == "Vertical": | |
| if round(vector_p1_p2.x, utils.precision()) == 0.0: | |
| obj.StraightDistance = vector_p1_p2.y | |
| obj.Placement.Base = v | |
| elif node_idx == 1: | |
| vector_p1_p2 = obj.Placement.inverse().multVec(v) - obj.Placement.inverse().multVec( | |
| obj.Placement.Base | |
| ) | |
| if obj.StraightDirection == "Horizontal": | |
| if round(vector_p1_p2.y, utils.precision()) == 0.0: | |
| obj.StraightDistance = vector_p1_p2.x | |
| else: | |
| obj.Placement.Base = obj.Placement.Base + (v - obj.Points[1]) | |
| elif obj.StraightDirection == "Vertical": | |
| if round(vector_p1_p2.x, utils.precision()) == 0.0: | |
| obj.StraightDistance = vector_p1_p2.y | |
| else: | |
| obj.Placement.Base = obj.Placement.Base + (v - obj.Points[1]) | |
| elif node_idx == 2: | |
| obj.TargetPoint = v | |
| else: | |
| pass # TODO : support custom direction with any number of points | |
| class DraftBezCurveGuiTools(GuiTools): | |
| def __init__(self): | |
| pass | |
| def get_edit_points(self, obj): | |
| editpoints = [] | |
| for p in obj.Points: | |
| editpoints.append(p) | |
| return editpoints | |
| def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): | |
| pts = obj.Points | |
| # DNC: check for coincident startpoint/endpoint to auto close the curve | |
| tol = 0.001 | |
| if ( | |
| ((node_idx == 0) and ((v - pts[-1]).Length < tol)) | |
| or (node_idx == len(pts) - 1) | |
| and ((v - pts[0]).Length < tol) | |
| ): | |
| obj.Closed = True | |
| # DNC: checks if point enter is equal to other, this could cause a OCC problem | |
| if v in pts: | |
| _err = translate("draft", "This object does not support possible " "coincident points") | |
| App.Console.PrintMessage(_err + "\n") | |
| return | |
| pts = self.recomputePointsBezier(obj, pts, node_idx, v, obj.Degree, moveTrackers=False) | |
| if obj.Closed: | |
| # check that the new point lies on the plane of the wire | |
| if hasattr(obj.Shape, "normalAt"): | |
| normal = obj.Shape.normalAt(0, 0) | |
| point_on_plane = obj.Shape.Vertexes[0].Point | |
| v.projectToPlane(point_on_plane, normal) | |
| pts[node_idx] = v | |
| obj.Points = pts | |
| def get_edit_point_context_menu(self, edit_command, obj, node_idx): | |
| return [ | |
| (translate("draft", "Delete Point"), lambda: self.delete_point(obj, node_idx)), | |
| (translate("draft", "Make Sharp"), lambda: self.smoothBezPoint(obj, node_idx, "Sharp")), | |
| ( | |
| translate("draft", "Make Tangent"), | |
| lambda: self.smoothBezPoint(obj, node_idx, "Tangent"), | |
| ), | |
| ( | |
| translate("draft", "Make Symmetric"), | |
| lambda: self.smoothBezPoint(obj, node_idx, "Symmetric"), | |
| ), | |
| ] | |
| def get_edit_obj_context_menu(self, edit_command, obj, position): | |
| return [ | |
| (translate("draft", "Add Point"), lambda: self.add_point(edit_command, obj, position)), | |
| (self.get_open_close_menu_text(obj), lambda: self.open_close_wire(obj)), | |
| (translate("draft", "Reverse Curve"), lambda: self.reverse_wire(obj)), | |
| ] | |
| def get_open_close_menu_text(self, obj): | |
| if obj.Closed: | |
| return translate("draft", "Open Curve") | |
| else: | |
| return translate("draft", "Close Curve") | |
| def init_preview_object(self, obj): | |
| return trackers.bezcurveTracker() | |
| def update_preview_object(self, edit_command, obj, node_idx, v): | |
| plist = edit_command.globalize_vectors(obj, obj.Points) | |
| pointList = self.recomputePointsBezier( | |
| obj, plist, node_idx, v, obj.Degree, moveTrackers=False | |
| ) | |
| edit_command.ghost.update(pointList, obj.Degree) | |
| def recomputePointsBezier(self, obj, pts, idx, v, degree, moveTrackers=False): | |
| """ | |
| (object, Points as list, nodeIndex as Int, App.Vector of new point, moveTrackers as Bool) | |
| return the new point list, applying the App.Vector to the given index point | |
| """ | |
| # DNC: allows one to close the curve by placing ends close to each other | |
| tol = 0.001 | |
| if ( | |
| ((idx == 0) and ((v - pts[-1]).Length < tol)) | |
| or (idx == len(pts) - 1) | |
| and ((v - pts[0]).Length < tol) | |
| ): | |
| obj.Closed = True | |
| # DNC: fix error message if edited point coincides with one of the existing points | |
| # if ( v in pts ) == False: | |
| knot = None | |
| ispole = idx % degree | |
| if ispole == 0: # knot | |
| if degree >= 3: | |
| if idx >= 1: # move left pole | |
| knotidx = idx if idx < len(pts) else 0 | |
| pts[idx - 1] = pts[idx - 1] + v - pts[knotidx] | |
| # if moveTrackers: # trackers are reset after editing | |
| # self.trackers[obj.Name][idx-1].set(pts[idx-1]) | |
| if idx < len(pts) - 1: # move right pole | |
| pts[idx + 1] = pts[idx + 1] + v - pts[idx] | |
| # if moveTrackers: | |
| # self.trackers[obj.Name][idx+1].set(pts[idx+1]) | |
| if idx == 0 and obj.Closed: # move last pole | |
| pts[-1] = pts[-1] + v - pts[idx] | |
| # if moveTrackers: | |
| # self.trackers[obj.Name][-1].set(pts[-1]) | |
| elif ispole == 1 and (idx >= 2 or obj.Closed): # right pole | |
| knot = idx - 1 | |
| changep = idx - 2 # -1 in case of closed curve | |
| elif ispole == degree - 1 and idx <= len(pts) - 3: # left pole | |
| knot = idx + 1 | |
| changep = idx + 2 | |
| elif ispole == degree - 1 and obj.Closed and idx == len(pts) - 1: # last pole | |
| knot = 0 | |
| changep = 1 | |
| if knot is not None: # we need to modify the opposite pole | |
| segment = int(knot / degree) - 1 | |
| cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0 | |
| if cont == 1: # tangent | |
| pts[changep] = obj.Proxy.modifytangentpole(pts[knot], v, pts[changep]) | |
| # if moveTrackers: | |
| # self.trackers[obj.Name][changep].set(pts[changep]) | |
| elif cont == 2: # symmetric | |
| pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot], v) | |
| # if moveTrackers: | |
| # self.trackers[obj.Name][changep].set(pts[changep]) | |
| pts[idx] = v | |
| return pts # returns the list of new points, taking into account knot continuity | |
| def smoothBezPoint(self, obj, point, style="Symmetric"): | |
| """called when changing the continuity of a knot""" | |
| style2cont = {"Sharp": 0, "Tangent": 1, "Symmetric": 2} | |
| if point is None: | |
| return | |
| if not (utils.get_type(obj) == "BezCurve"): | |
| return | |
| pts = obj.Points | |
| deg = obj.Degree | |
| if deg < 2: | |
| return | |
| if point % deg != 0: # point is a pole | |
| if deg >= 3: # allow one to select poles | |
| if (point % deg == 1) and (point > 2 or obj.Closed): # right pole | |
| knot = point - 1 | |
| keepp = point | |
| changep = point - 2 | |
| elif point < len(pts) - 3 and point % deg == deg - 1: # left pole | |
| knot = point + 1 | |
| keepp = point | |
| changep = point + 2 | |
| elif point == len(pts) - 1 and obj.Closed: # last pole | |
| # if the curve is closed the last pole has the last | |
| # index in the points lists | |
| knot = 0 | |
| keepp = point | |
| changep = 1 | |
| else: | |
| App.Console.PrintWarning( | |
| translate("draft", "Cannot change knot belonging to pole %d" % point) + "\n" | |
| ) | |
| return | |
| if knot: | |
| if style == "Tangent": | |
| pts[changep] = obj.Proxy.modifytangentpole( | |
| pts[knot], pts[keepp], pts[changep] | |
| ) | |
| elif style == "Symmetric": | |
| pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot], pts[keepp]) | |
| else: # sharp | |
| pass # | |
| else: | |
| App.Console.PrintWarning(translate("draft", "Selection is not a knot") + "\n") | |
| return | |
| else: # point is a knot | |
| if style == "Sharp": | |
| if obj.Closed and point == len(pts) - 1: | |
| knot = 0 | |
| else: | |
| knot = point | |
| elif style == "Tangent" and point > 0 and point < len(pts) - 1: | |
| prev, next = obj.Proxy.tangentpoles(pts[point], pts[point - 1], pts[point + 1]) | |
| pts[point - 1] = prev | |
| pts[point + 1] = next | |
| knot = point # index for continuity | |
| elif style == "Symmetric" and point > 0 and point < len(pts) - 1: | |
| prev, next = obj.Proxy.symmetricpoles(pts[point], pts[point - 1], pts[point + 1]) | |
| pts[point - 1] = prev | |
| pts[point + 1] = next | |
| knot = point # index for continuity | |
| elif obj.Closed and (style == "Symmetric" or style == "Tangent"): | |
| if style == "Tangent": | |
| pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1]) | |
| elif style == "Symmetric": | |
| pts[1], pts[-1] = obj.Proxy.symmetricpoles(pts[0], pts[1], pts[-1]) | |
| knot = 0 | |
| else: | |
| App.Console.PrintWarning( | |
| translate("draft", "Endpoint of Bézier curve cannot be smoothed") + "\n" | |
| ) | |
| return | |
| segment = knot // deg # segment index | |
| newcont = obj.Continuity[:] # don't edit a property inplace !!! | |
| if not obj.Closed and (len(obj.Continuity) == segment - 1 or segment == 0): | |
| pass # open curve | |
| elif ( | |
| len(obj.Continuity) >= segment | |
| or obj.Closed | |
| and segment == 0 | |
| and len(obj.Continuity) > 1 | |
| ): | |
| newcont[segment - 1] = style2cont.get(style) | |
| else: # should not happen | |
| App.Console.PrintWarning( | |
| "Continuity indexing error:" | |
| + "point:%d deg:%d len(cont):%d" % (knot, deg, len(obj.Continuity)) | |
| ) | |
| obj.Points = pts | |
| obj.Continuity = newcont | |
| def add_point(self, edit_command, obj, pos): | |
| """Add point to obj and reset trackers.""" | |
| info, pt = edit_command.get_specific_object_info(obj, pos) | |
| if not info or (pt is None): | |
| return | |
| import Part | |
| pts = obj.Points | |
| if not info["Component"].startswith("Edge"): | |
| return # clicked control point | |
| edgeindex = int(info["Component"].lstrip("Edge")) - 1 | |
| wire = obj.Shape.Wires[0] | |
| bz = wire.Edges[edgeindex].Curve | |
| param = bz.parameter(pt) | |
| seg1 = wire.Edges[edgeindex].copy().Curve | |
| seg2 = wire.Edges[edgeindex].copy().Curve | |
| seg1.segment(seg1.FirstParameter, param) | |
| seg2.segment(param, seg2.LastParameter) | |
| if edgeindex == len(wire.Edges): | |
| # we hit the last segment, we need to fix the degree | |
| degree = wire.Edges[0].Curve.Degree | |
| seg1.increase(degree) | |
| seg2.increase(degree) | |
| edges = ( | |
| wire.Edges[0:edgeindex] | |
| + [Part.Edge(seg1), Part.Edge(seg2)] | |
| + wire.Edges[edgeindex + 1 :] | |
| ) | |
| pts = edges[0].Curve.getPoles()[0:1] | |
| for edge in edges: | |
| pts.extend(edge.Curve.getPoles()[1:]) | |
| if obj.Closed: | |
| pts.pop() | |
| c = obj.Continuity | |
| # assume we have a tangent continuity for an arbitrarily split | |
| # segment, unless it's linear | |
| cont = 1 if (obj.Degree >= 2) else 0 | |
| obj.Continuity = c[0:edgeindex] + [cont] + c[edgeindex:] | |
| obj.Points = pts | |
| obj.recompute() | |
| def delete_point(self, obj, node_idx): | |
| if len(obj.Points) <= 2: | |
| _err(translate("draft", "Active object must have more than two points/nodes")) | |
| return | |
| pts = obj.Points | |
| pts.pop(node_idx) | |
| obj.Points = pts | |
| obj.Proxy.resetcontinuity(obj) | |
| obj.recompute() | |
| def open_close_wire(self, obj): | |
| obj.Closed = not obj.Closed | |
| obj.recompute() | |
| def reverse_wire(self, obj): | |
| obj.Points = reversed(obj.Points) | |
| obj.recompute() | |
| ## @} | |