| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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 |
| | """ |
| | |
| | |
| | |
| |
|
| | __title__ = "FreeCAD Draft Edit Tool" |
| | __author__ = ( |
| | "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " "Dmitry Chigrin, Carlo Pavan" |
| | ) |
| | __url__ = "https://www.freecad.org" |
| |
|
| | |
| | |
| | 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 |
| | if node_idx == 0 and (v - pts[-1]).Length < tol: |
| | |
| | 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: |
| | |
| | obj.Closed = True |
| | del pts[-1] |
| | obj.Points = pts |
| | return |
| | elif v in pts: |
| | |
| | _err = translate("draft", "This object does not support possible " "coincident points") |
| | App.Console.PrintMessage(_err + "\n") |
| | return |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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"): |
| | |
| | |
| | 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): |
| | |
| | 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 |
| | |
| | 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: |
| | |
| | editpoints.append(App.Vector(obj.Radius, 0, 0)) |
| | else: |
| | |
| | editpoints.append(self.getArcStart(obj)) |
| | editpoints.append(self.getArcEnd(obj)) |
| | editpoints.append(self.getArcMid(obj)) |
| | return editpoints |
| |
|
| | def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): |
| | if obj.FirstAngle == obj.LastAngle: |
| | |
| | if node_idx == 0: |
| | obj.Placement.Base = obj.Placement.multVec(v) |
| | elif node_idx == 1: |
| | obj.Radius = v.Length |
| |
|
| | else: |
| | |
| | if alt_edit_mode == 0: |
| | import Part |
| |
|
| | if node_idx == 0: |
| | |
| | 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: |
| | p1 = v |
| | elif node_idx == 3: |
| | p2 = v |
| | elif node_idx == 2: |
| | p3 = v |
| |
|
| | arc = Part.ArcOfCircle(p1, p2, p3) |
| | import Part |
| |
|
| | s = arc.toShape() |
| | |
| | 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: |
| | |
| | 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: |
| | return [ |
| | ( |
| | translate("draft", "Move Arc"), |
| | lambda: self.handle_move_arc(edit_command, obj, node_idx), |
| | ), |
| | ] |
| | elif node_idx == 1: |
| | return [ |
| | ( |
| | translate("draft", "Set First Angle"), |
| | lambda: self.handle_set_first_angle(edit_command, obj, node_idx), |
| | ), |
| | ] |
| | elif node_idx == 2: |
| | return [ |
| | ( |
| | translate("draft", "Set Last Angle"), |
| | lambda: self.handle_set_last_angle(edit_command, obj, node_idx), |
| | ), |
| | ] |
| | elif node_idx == 3: |
| | 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): |
| | |
| | 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: |
| | |
| | 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: |
| | |
| | if node_idx == 0: |
| | |
| | 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_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): |
| | if utils.get_type(obj) == "Circle": |
| | return self.pointOnCircle(obj, obj.FirstAngle, global_placement) |
| |
|
| | def getArcEnd(self, obj, global_placement=False): |
| | if utils.get_type(obj) == "Circle": |
| | return self.pointOnCircle(obj, obj.LastAngle, global_placement) |
| |
|
| | def getArcMid(self, obj, global_placement=False): |
| | 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): |
| | |
| | editpoints[1] = 0.9 * editpoints[1] + 0.1 * editpoints[2] |
| | else: |
| | pass |
| | 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 |
| |
|
| |
|
| | 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 |
| | |
| | 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 |
| | |
| | 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: |
| | |
| | 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 |
| | """ |
| | |
| | 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 |
| | |
| | |
| | knot = None |
| | ispole = idx % degree |
| |
|
| | if ispole == 0: |
| | if degree >= 3: |
| | if idx >= 1: |
| | knotidx = idx if idx < len(pts) else 0 |
| | pts[idx - 1] = pts[idx - 1] + v - pts[knotidx] |
| | |
| | |
| | if idx < len(pts) - 1: |
| | pts[idx + 1] = pts[idx + 1] + v - pts[idx] |
| | |
| | |
| | if idx == 0 and obj.Closed: |
| | pts[-1] = pts[-1] + v - pts[idx] |
| | |
| | |
| |
|
| | elif ispole == 1 and (idx >= 2 or obj.Closed): |
| | knot = idx - 1 |
| | changep = idx - 2 |
| |
|
| | elif ispole == degree - 1 and idx <= len(pts) - 3: |
| | knot = idx + 1 |
| | changep = idx + 2 |
| |
|
| | elif ispole == degree - 1 and obj.Closed and idx == len(pts) - 1: |
| | knot = 0 |
| | changep = 1 |
| |
|
| | if knot is not None: |
| | segment = int(knot / degree) - 1 |
| | cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0 |
| | if cont == 1: |
| | pts[changep] = obj.Proxy.modifytangentpole(pts[knot], v, pts[changep]) |
| | |
| | |
| | elif cont == 2: |
| | pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot], v) |
| | |
| | |
| | pts[idx] = v |
| |
|
| | return pts |
| |
|
| | 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: |
| | if deg >= 3: |
| | if (point % deg == 1) and (point > 2 or obj.Closed): |
| | knot = point - 1 |
| | keepp = point |
| | changep = point - 2 |
| | elif point < len(pts) - 3 and point % deg == deg - 1: |
| | knot = point + 1 |
| | keepp = point |
| | changep = point + 2 |
| | elif point == len(pts) - 1 and obj.Closed: |
| | |
| | |
| | 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: |
| | pass |
| | else: |
| | App.Console.PrintWarning(translate("draft", "Selection is not a knot") + "\n") |
| | return |
| | else: |
| | 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 |
| | 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 |
| | 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 |
| | newcont = obj.Continuity[:] |
| | if not obj.Closed and (len(obj.Continuity) == segment - 1 or segment == 0): |
| | pass |
| | elif ( |
| | len(obj.Continuity) >= segment |
| | or obj.Closed |
| | and segment == 0 |
| | and len(obj.Continuity) > 1 |
| | ): |
| | newcont[segment - 1] = style2cont.get(style) |
| | else: |
| | 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 |
| | 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): |
| | |
| | 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 |
| | |
| | |
| | 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() |
| |
|
| |
|
| | |
| |
|