From 4e8124e415a3570ba1f67e5130d86435f6a226b7 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 23 May 2020 19:33:39 +0200 Subject: [PATCH] Draft: Split Draft Edit into 4 modules One for interaction handling and 3 for object editing functions, divided between Draft, Part, Arch --- src/Mod/Draft/CMakeLists.txt | 3 + src/Mod/Draft/draftguitools/gui_edit.py | 773 ++---------------- .../draftguitools/gui_edit_arch_objects.py | 227 +++++ .../draftguitools/gui_edit_draft_objects.py | 482 +++++++++++ .../draftguitools/gui_edit_part_objects.py | 79 ++ 5 files changed, 841 insertions(+), 723 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_edit_arch_objects.py create mode 100644 src/Mod/Draft/draftguitools/gui_edit_draft_objects.py create mode 100644 src/Mod/Draft/draftguitools/gui_edit_part_objects.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index ee3516b741..dbcc53db8b 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -230,6 +230,9 @@ SET(Draft_GUI_tools draftguitools/gui_snaps.py draftguitools/gui_snapper.py draftguitools/gui_trackers.py + draftguitools/gui_edit_draft_objects.py + draftguitools/gui_edit_arch_objects.py + draftguitools/gui_edit_part_objects.py draftguitools/gui_edit.py draftguitools/gui_lineops.py draftguitools/gui_togglemodes.py diff --git a/src/Mod/Draft/draftguitools/gui_edit.py b/src/Mod/Draft/draftguitools/gui_edit.py index a94af6c1b2..ab1012f46a 100644 --- a/src/Mod/Draft/draftguitools/gui_edit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -50,6 +50,10 @@ import DraftGeomUtils from draftutils.translate import translate import draftguitools.gui_trackers as trackers +import draftguitools.gui_edit_draft_objects as edit_draft +import draftguitools.gui_edit_arch_objects as edit_arch +import draftguitools.gui_edit_part_objects as edit_part + COLORS = { "default": Gui.draftToolBar.getDefaultColor("snap"), @@ -431,10 +435,10 @@ class Edit(gui_base_original.Modifier): self.display_tracker_menu(event) if key == 105: # "i" if utils.get_type(self.obj) == "Circle": - self.arcInvert(self.obj) - #if key == 65535: # BUG: delete key activate Std::Delete command at the same time! - # print("DELETE PRESSED\n") - # self.delPoint(event) + edit_draft.arcInvert(self.obj) + if key == 65535 and Gui.Selection.GetSelection() is None: # BUG: delete key activate Std::Delete command at the same time! + print("DELETE PRESSED\n") + self.delPoint(event) def mousePressed(self, event_callback): """ @@ -673,7 +677,7 @@ class Edit(gui_base_original.Modifier): return self.trackers[obj.Name] = [] if utils.get_type(obj) == "BezCurve": - self.resetTrackersBezier(obj) + edit_draft.resetTrackersBezier(obj) else: if obj.Name in self.trackers: self.removeTrackers(obj) @@ -766,7 +770,7 @@ class Edit(gui_base_original.Modifier): elif utils.get_type(obj) == "BezCurve": self.ghost.on() plist = self.applyPlacement(obj.Points) - pointList = self.recomputePointsBezier(obj,plist,idx,pt,obj.Degree,moveTrackers=True) + pointList = edit_draft.recomputePointsBezier(obj,plist,idx,pt,obj.Degree,moveTrackers=True) self.ghost.update(pointList,obj.Degree) elif utils.get_type(obj) == "Circle": self.ghost.on() @@ -785,10 +789,9 @@ class Edit(gui_base_original.Modifier): # edit by 3 points if self.editing == 0: # center point - import DraftVecUtils p1 = self.invpl.multVec(self.obj.Shape.Vertexes[0].Point) p2 = self.invpl.multVec(self.obj.Shape.Vertexes[1].Point) - p0 = DraftVecUtils.project(self.invpl.multVec(pt),self.invpl.multVec(self.getArcMid(obj, global_placement=True))) + p0 = DraftVecUtils.project(self.invpl.multVec(pt),self.invpl.multVec(edit_draft.getArcMid(obj, global_placement=True))) self.ghost.autoinvert=False self.ghost.setRadius(p1.sub(p0).Length) self.ghost.setStartPoint(self.obj.Shape.Vertexes[1].Point) @@ -796,9 +799,9 @@ class Edit(gui_base_original.Modifier): self.ghost.setCenter(self.pl.multVec(p0)) return else: - p1 = self.getArcStart(obj, global_placement=True) - p2 = self.getArcMid(obj, global_placement=True) - p3 = self.getArcEnd(obj, global_placement=True) + p1 = edit_draft.getArcStart(obj, global_placement=True) + p2 = edit_draft.getArcMid(obj, global_placement=True) + p3 = edit_draft.getArcEnd(obj, global_placement=True) if self.editing == 1: p1=pt elif self.editing == 3: @@ -980,7 +983,8 @@ class Edit(gui_base_original.Modifier): # ------------------------------------------------------------------------- def setEditPoints(self, obj): - """append given object's editpoints to self.edipoints and set EditTrackers""" + """Append given object's editpoints to self.edipoints and set EditTrackers + """ self.setPlacement(obj) self.editpoints = self.getEditPoints(obj) if self.editpoints: # set trackers and align plane @@ -988,44 +992,44 @@ class Edit(gui_base_original.Modifier): self.editpoints = [] def getEditPoints(self, obj): - """Return a list of App.Vectors relative to object edit nodes. + """Return a list of App.Vectors according to the given object edit nodes. """ objectType = utils.get_type(obj) if objectType in ["Wire", "BSpline"]: self.ui.editUi("Wire") - return self.getWirePts(obj) + return edit_draft.getWirePts(obj) elif objectType == "BezCurve": self.ui.editUi("BezCurve") - self.resetTrackersBezier(obj) + edit_draft.resetTrackersBezier(obj) self.editpoints = [] return elif objectType == "Circle": - return self.getCirclePts(obj) + return edit_draft.getCirclePts(obj) elif objectType == "Rectangle": - return self.getRectanglePts(obj) + return edit_draft.getRectanglePts(obj) elif objectType == "Polygon": - return self.getPolygonPts(obj) + return edit_draft.getPolygonPts(obj) elif objectType in ("Dimension","LinearDimension"): - return self.getDimensionPts(obj) + return edit_draft.getDimensionPts(obj) elif objectType == "Wall": - return self.getWallPts(obj) + return edit_arch.getWallPts(obj) elif objectType == "Window": - return self.getWindowPts(obj) + return edit_arch.getWindowPts(obj) elif objectType == "Space": - return self.getSpacePts(obj) + return edit_arch.getSpacePts(obj) elif objectType == "Structure": - return self.getStructurePts(obj) + return edit_arch.getStructurePts(obj) elif objectType == "PanelCut": - return self.getPanelCutPts(obj) + return edit_arch.getPanelCutPts(obj) elif objectType == "PanelSheet": - return self.getPanelSheetPts(obj) + return edit_arch.getPanelSheetPts(obj) elif objectType == "Part" and obj.TypeId == "Part::Box": - return self.getPartBoxPts(obj) + return edit_part.getPartBoxPts(obj) elif objectType == "Part::Line" and obj.TypeId == "Part::Line": - return self.getPartLinePts(obj) + return edit_part.getPartLinePts(obj) elif objectType == "Sketch": - return self.getSketchPts(obj) + return edit_arch.getSketchPts(obj) else: return None @@ -1036,35 +1040,35 @@ class Edit(gui_base_original.Modifier): App.ActiveDocument.openTransaction("Edit") if objectType in ["Wire", "BSpline"]: - self.updateWire(obj, nodeIndex, v) + edit_draft.updateWire(obj, nodeIndex, v) elif objectType == "BezCurve": - self.updateWire(obj, nodeIndex, v) + edit_draft.updateWire(obj, nodeIndex, v) elif objectType == "Circle": - self.updateCircle(obj, nodeIndex, v) + edit_draft.updateCircle(obj, nodeIndex, v, self.alt_edit_mode) elif objectType == "Rectangle": - self.updateRectangle(obj, nodeIndex, v) + edit_draft.updateRectangle(obj, nodeIndex, v) elif objectType == "Polygon": - self.updatePolygon(obj, nodeIndex, v) + edit_draft.updatePolygon(obj, nodeIndex, v) elif objectType in ("Dimension","LinearDimension"): - self.updateDimension(obj, nodeIndex, v) + edit_draft.updateDimension(obj, nodeIndex, v) elif objectType == "Sketch": - self.updateSketch(obj, nodeIndex, v) + edit_arch.updateSketch(obj, nodeIndex, v) elif objectType == "Wall": - self.updateWall(obj, nodeIndex, v) + edit_arch.updateWall(obj, nodeIndex, v) elif objectType == "Window": - self.updateWindow(obj, nodeIndex, v) + edit_arch.updateWindow(obj, nodeIndex, v) elif objectType == "Space": - self.updateSpace(obj, nodeIndex, v) + edit_arch.updateSpace(obj, nodeIndex, v) elif objectType == "Structure": - self.updateStructure(obj, nodeIndex, v) + edit_arch.updateStructure(obj, nodeIndex, v) elif objectType == "PanelCut": - self.updatePanelCut(obj, nodeIndex, v) + edit_arch.updatePanelCut(obj, nodeIndex, v) elif objectType == "PanelSheet": - self.updatePanelSheet(obj, nodeIndex, v) + edit_arch.updatePanelSheet(obj, nodeIndex, v) elif objectType == "Part::Line" and self.obj.TypeId == "Part::Line": - self.updatePartLine(obj, nodeIndex, v) + edit_arch.updatePartLine(obj, nodeIndex, v) elif objectType == "Part" and self.obj.TypeId == "Part::Box": - self.updatePartBox(obj, nodeIndex, v) + edit_arch.updatePartBox(obj, nodeIndex, v) obj.recompute() App.ActiveDocument.commitTransaction() @@ -1074,683 +1078,6 @@ class Edit(gui_base_original.Modifier): except AttributeError as err: pass - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve - # ------------------------------------------------------------------------- - - def getWirePts(self, obj): - editpoints = [] - for p in obj.Points: - p = obj.getGlobalPlacement().multVec(p) - editpoints.append(p) - return editpoints - - def updateWire(self, obj, nodeIndex, v): - pts = obj.Points - editPnt = obj.getGlobalPlacement().inverse().multVec(v) - # DNC: allows to close the curve by placing ends close to each other - tol = 0.001 - if ( ( nodeIndex == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( - nodeIndex == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): - obj.Closed = True - # DNC: fix error message if edited point coincides with one of the existing points - if ( editPnt in pts ) == True: # checks if point enter is equal to other, this could cause a OCC problem - App.Console.PrintMessage(translate("draft", - "This object does not support possible " - "coincident points, please try again.") - + "\n") - if utils.get_type(obj) in ["BezCurve"]: - self.resetTrackers(obj) - else: - self.trackers[obj.Name][nodeIndex].set(obj.getGlobalPlacement(). - multVec(obj.Points[nodeIndex])) - return - if utils.get_type(obj) in ["BezCurve"]: - pts = self.recomputePointsBezier(obj,pts,nodeIndex,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 - print(v) - v.projectToPlane(point_on_plane, normal) - print(v) - editPnt = obj.getGlobalPlacement().inverse().multVec(v) - pts[nodeIndex] = editPnt - obj.Points = pts - self.trackers[obj.Name][nodeIndex].set(v) - - def recomputePointsBezier(self, obj, pts, idx, v, - degree, moveTrackers=True): - """ - (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 - """ - editPnt = v - # DNC: allows to close the curve by placing ends close to each other - tol = 0.001 - if ( ( idx == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( - idx == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): - obj.Closed = True - # DNC: fix error message if edited point coincides with one of the existing points - #if ( editPnt 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] + editPnt - pts[knotidx] - if moveTrackers: - self.trackers[obj.Name][idx-1].set(pts[idx-1]) - if idx < len(pts)-1: #move right pole - pts[idx+1] = pts[idx+1] + editPnt - 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] + editPnt -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], - editPnt,pts[changep]) - if moveTrackers: - self.trackers[obj.Name][changep].set(pts[changep]) - elif cont == 2: #symmetric - pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt) - 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 resetTrackersBezier(self, obj): - # in future move tracker definition to DraftTrackers - from pivy import coin - knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp - coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent - coin.SoMarkerSet.HOURGLASS_FILLED_9_9) #symmetric - polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9 #pole - self.trackers[obj.Name] = [] - cont = obj.Continuity - firstknotcont = cont[-1] if (obj.Closed and cont) else 0 - pointswithmarkers = [(obj.Shape.Edges[0].Curve. - getPole(1),knotmarkers[firstknotcont])] - for edgeindex, edge in enumerate(obj.Shape.Edges): - poles = edge.Curve.getPoles() - pointswithmarkers.extend([(point,polemarker) for point in poles[1:-1]]) - if not obj.Closed or len(obj.Shape.Edges) > edgeindex +1: - knotmarkeri = cont[edgeindex] if len(cont) > edgeindex else 0 - pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri])) - for index, pwm in enumerate(pointswithmarkers): - p, marker = pwm - # if self.pl: p = self.pl.multVec(p) - self.trackers[obj.Name].append(trackers.editTracker(p,obj.Name, - index,obj.ViewObject.LineColor,marker=marker)) - - 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 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", - "Can't 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 BezCurve can't 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 - self.resetTrackers(obj) - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Rectangle - # ------------------------------------------------------------------------- - - def getRectanglePts(self, obj): - """Return the list of edipoints for the given Draft Rectangle. - - 0 : Placement.Base - 1 : Length - 2 : Height - """ - editpoints = [] - editpoints.append(obj.getGlobalPlacement().Base) - editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0))) - editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0))) - return editpoints - - def updateRectangleTrackers(self, obj): - self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base) - self.trackers[obj.Name][1].set(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0))) - self.trackers[obj.Name][2].set(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0))) - - def updateRectangle(self, obj, nodeIndex, v): - import DraftVecUtils - delta = obj.getGlobalPlacement().inverse().multVec(v) - if nodeIndex == 0: - # p = obj.getGlobalPlacement() - # p.move(delta) - obj.Placement.move(delta) - elif self.editing == 1: - obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length - elif self.editing == 2: - obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length - self.updateRectangleTrackers(obj) - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented) - # ------------------------------------------------------------------------- - - def setEllipsePts(self): - return - - def updateEllipse(self,v): - return - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Circle/Arc - # ------------------------------------------------------------------------- - - def getCirclePts(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(obj.getGlobalPlacement().Base) - if obj.FirstAngle == obj.LastAngle: - # obj is a circle - self.ui.editUi("Circle") - editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Radius,0,0))) - else: - # obj is an arc - self.ui.editUi("Arc") - editpoints.append(self.getArcStart(obj, global_placement=True))#First endpoint - editpoints.append(self.getArcEnd(obj, global_placement=True))#Second endpoint - editpoints.append(self.getArcMid(obj, global_placement=True))#Midpoint - return editpoints - - def updateCircleTrackers(self, obj): - self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base) - self.trackers[obj.Name][1].set(self.getArcStart(obj, global_placement=True)) - if len(self.trackers[obj.Name]) > 2: - # object is an arc - self.trackers[obj.Name][2].set(self.getArcEnd(obj, global_placement=True)) - self.trackers[obj.Name][3].set(self.getArcMid(obj, global_placement=True)) - - def updateCircle(self, obj, nodeIndex, v): - delta = obj.getGlobalPlacement().inverse().multVec(v) - local_v = obj.Placement.multVec(delta) - - if obj.FirstAngle == obj.LastAngle: - # object is a circle - if nodeIndex == 0: - obj.Placement.Base = local_v - elif nodeIndex == 1: - obj.Radius = delta.Length - - else: - # obj is an arc - if self.alt_edit_mode == 0: - # edit arc by 3 points - import Part - if nodeIndex == 0: - # center point - import DraftVecUtils - p1 = self.getArcStart(obj) - p2 = self.getArcEnd(obj) - p0 = DraftVecUtils.project(delta,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) - self.setPlacement(obj) - - else: - if nodeIndex == 1: # first point - p1=v - p2=self.getArcMid(obj,global_placement=True) - p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 3: # midpoint - p1=self.getArcStart(obj,global_placement=True) - p2=v - p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 2: # second point - p1=self.getArcStart(obj,global_placement=True) - p2=self.getArcMid(obj,global_placement=True) - p3=v - arc=Part.ArcOfCircle(p1,p2,p3) - obj.Placement.Base = obj.Placement.multVec(obj.getGlobalPlacement().inverse().multVec(arc.Location)) - self.setPlacement(obj) - obj.Radius = arc.Radius - delta = self.invpl.multVec(p1) - obj.FirstAngle = math.degrees(math.atan2(delta[1],delta[0])) - delta = self.invpl.multVec(p3) - obj.LastAngle = math.degrees(math.atan2(delta[1],delta[0])) - - elif self.alt_edit_mode == 1: - # edit arc by center radius FirstAngle LastAngle - if nodeIndex == 0: - obj.Placement.Base = local_v - self.setPlacement(obj) - else: - dangle = math.degrees(math.atan2(delta[1],delta[0])) - if nodeIndex == 1: - obj.FirstAngle = dangle - elif nodeIndex == 2: - obj.LastAngle = dangle - elif nodeIndex == 3: - obj.Radius = delta.Length - - obj.recompute() - self.updateCircleTrackers(obj) - - - 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 == True: - p = obj.getGlobalPlacement().multVec(p) - return p - return None - - def arcInvert(self, obj): - obj.FirstAngle, obj.LastAngle = obj.LastAngle, obj.FirstAngle - obj.recompute() - self.updateCircleTrackers(obj) - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon) - # ------------------------------------------------------------------------- - - def getPolygonPts(self, obj): - editpoints = [] - editpoints.append(obj.Placement.Base) - editpoints.append(obj.Shape.Vertexes[0].Point) - return editpoints - - def updatePolygon(self, obj, nodeIndex, v): - delta = v.sub(self.obj.Placement.Base) - if self.editing == 0: - p = self.obj.Placement - p.move(delta) - self.obj.Placement = p - self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) - elif self.editing == 1: - if self.obj.DrawMode == 'inscribed': - self.obj.Radius = delta.Length - else: - halfangle = ((math.pi*2)/self.obj.FacesNumber)/2 - rad = math.cos(halfangle)*delta.Length - self.obj.Radius = rad - self.obj.recompute() - self.trackers[self.obj.Name][1].set(self.obj.Shape.Vertexes[0].Point) - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable) - # ------------------------------------------------------------------------- - - def getDimensionPts(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 updateDimension(self, obj, nodeIndex, v): - if self.editing == 0: - self.obj.Start = v - elif self.editing == 1: - self.obj.End = v - elif self.editing == 2: - self.obj.Dimline = v - elif self.editing == 3: - self.obj.ViewObject.TextPosition = v - - # ------------------------------------------------------------------------- - # EDIT OBJECT TOOLS : ARCH Wall, Windows, Structure, Panel, etc. - # ------------------------------------------------------------------------- - - # SKETCH: just if it's composed by a single segment----------------------- - - def getSketchPts(self, obj): - """Return the list of edipoints for the given single line sketch. - - (WallTrace) - 0 : startpoint - 1 : endpoint - """ - editpoints = [] - if obj.GeometryCount == 1: - editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,1))) - editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,2))) - return editpoints - else: - App.Console.PrintWarning(translate("draft", - "Sketch is too complex to edit: " - "it is suggested to use sketcher default editor") - + "\n") - return None - - def updateSketch(self, obj, nodeIndex, v): - """Move a single line sketch vertex a certain displacement. - - (single segment sketch object, node index as Int, App.Vector) - move a single line sketch (WallTrace) vertex according to a given App.Vector - 0 : startpoint - 1 : endpoint - """ - if nodeIndex == 0: - obj.movePoint(0,1,obj.getGlobalPlacement().inverse().multVec(v)) - elif nodeIndex == 1: - obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v)) - obj.recompute() - - # WALL--------------------------------------------------------------------- - - def getWallPts(self, obj): - """Return the list of edipoints for the given Arch Wall object. - - 0 : height of the wall - 1-to end : base object editpoints, in place with the wall - """ - editpoints = [] - # height of the wall - editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height))) - # try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker) - if obj.Base: - # base points are added to self.trackers under wall-name key - basepoints = [] - if utils.get_type(obj.Base) in ["Wire","Circle","Rectangle", - "Polygon", "Sketch"]: - basepoints = self.getEditPoints(obj.Base) - for point in basepoints: - editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? - return editpoints - - def updateWallTrackers(self, obj): - """Update self.trackers[obj.Name][0] to match with given object.""" - pass - - def updateWall(self, obj, nodeIndex, v): - if nodeIndex == 0: - delta= obj.getGlobalPlacement().inverse().multVec(v) - vz=DraftVecUtils.project(delta,App.Vector(0, 0, 1)) - if vz.Length > 0: - obj.Height = vz.Length - elif nodeIndex > 0: - if obj.Base: - if utils.get_type(obj.Base) in ["Wire", "Circle", "Rectangle", - "Polygon", "Sketch"]: - self.update(obj.Base, nodeIndex - 1, - obj.Placement.inverse().multVec(v)) - obj.recompute() - - # WINDOW------------------------------------------------------------------- - - def getWindowPts(self, obj): - editpoints = [] - pos = obj.Base.Placement.Base - h = float(obj.Height) + pos.z - normal = obj.Normal - angle = normal.getAngle(App.Vector(1, 0, 0)) - editpoints.append(pos) - editpoints.append(App.Vector(pos.x + float(obj.Width) * math.cos(angle-math.pi / 2.0), - pos.y + float(obj.Width) * math.sin(angle-math.pi / 2.0), - pos.z)) - editpoints.append(App.Vector(pos.x, pos.y, h)) - return editpoints - - def updateWindow(self, obj, nodeIndex, v): - pos = self.obj.Base.Placement.Base - if self.editing == 0: - self.obj.Base.Placement.Base = v - self.obj.Base.recompute() - if self.editing == 1: - self.obj.Width = pos.sub(v).Length - self.obj.Base.recompute() - if self.editing == 2: - self.obj.Height = pos.sub(v).Length - self.obj.Base.recompute() - for obj in self.obj.Hosts: - obj.recompute() - self.obj.recompute() - - # STRUCTURE---------------------------------------------------------------- - - def getStructurePts(self, obj): - if obj.Nodes: - editpoints = [] - self.originalDisplayMode = obj.ViewObject.DisplayMode - self.originalPoints = obj.ViewObject.NodeSize - self.originalNodes = obj.ViewObject.ShowNodes - self.obj.ViewObject.DisplayMode = "Wireframe" - self.obj.ViewObject.NodeSize = 1 - # self.obj.ViewObject.ShowNodes = True - for p in obj.Nodes: - if self.pl: - p = self.pl.multVec(p) - editpoints.append(p) - return editpoints - else: - return None - - def updateStructure(self, obj, nodeIndex, v): - nodes = self.obj.Nodes - nodes[self.editing] = self.invpl.multVec(v) - self.obj.Nodes = nodes - - # SPACE-------------------------------------------------------------------- - - def getSpacePts(self, obj): - try: - editpoints = [] - self.editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject)) - return editpoints - except: - pass - - def updateSpace(self, obj, nodeIndex, v): - if self.editing == 0: - self.obj.ViewObject.TextPosition = v - - # PANELS------------------------------------------------------------------- - - def getPanelCutPts(self, obj): - editpoints = [] - if self.obj.TagPosition.Length == 0: - pos = obj.Shape.BoundBox.Center - else: - pos = self.pl.multVec(obj.TagPosition) - editpoints.append(pos) - return editpoints - - def updatePanelCut(self, obj, nodeIndex, v): - if self.editing == 0: - self.obj.TagPosition = self.invpl.multVec(v) - - def getPanelSheetPts(self, obj): - editpoints = [] - editpoints.append(self.pl.multVec(obj.TagPosition)) - for o in obj.Group: - editpoints.append(self.pl.multVec(o.Placement.Base)) - return editpoints - - def updatePanelSheet(self, obj, nodeIndex, v): - if self.editing == 0: - self.obj.TagPosition = self.invpl.multVec(v) - else: - self.obj.Group[self.editing-1].Placement.Base = self.invpl.multVec(v) - - # PART::LINE-------------------------------------------------------------- - - def getPartLinePts(self, obj): - editpoints = [] - editpoints.append(self.pl.multVec(App.Vector(obj.X1,obj.Y1,obj.Z1))) - editpoints.append(self.pl.multVec(App.Vector(obj.X2,obj.Y2,obj.Z2))) - return editpoints - - def updatePartLine(self, obj, nodeIndex, v): - pt=self.invpl.multVec(v) - if self.editing == 0: - self.obj.X1 = pt.x - self.obj.Y1 = pt.y - self.obj.Z1 = pt.z - elif self.editing == 1: - self.obj.X2 = pt.x - self.obj.Y2 = pt.y - self.obj.Z2 = pt.z - - # PART::BOX--------------------------------------------------------------- - - def getPartBoxPts(self, obj): - editpoints = [] - editpoints.append(obj.Placement.Base) - editpoints.append(self.pl.multVec(App.Vector(obj.Length, 0, 0))) - editpoints.append(self.pl.multVec(App.Vector(0, obj.Width, 0))) - editpoints.append(self.pl.multVec(App.Vector(0, 0, obj.Height))) - return editpoints - - def updatePartBox(self, obj, nodeIndex, v): - delta = self.invpl.multVec(v) - if self.editing == 0: - self.obj.Placement.Base = v - self.setPlacement(self.obj) - elif self.editing == 1: - _vector = DraftVecUtils.project(delta, App.Vector(1, 0, 0)) - self.obj.Length = _vector.Length - elif self.editing == 2: - _vector = DraftVecUtils.project(delta, App.Vector(0, 1, 0)) - self.obj.Width = _vector.Length - elif self.editing == 3: - _vector = DraftVecUtils.project(delta, App.Vector(0, 0, 1)) - self.obj.Height = _vector.Length - self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) - self.trackers[self.obj.Name][1].set(self.pl.multVec(App.Vector(self.obj.Length,0,0))) - self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0))) - self.trackers[self.obj.Name][3].set(self.pl.multVec(App.Vector(0,0,self.obj.Height))) # ------------------------------------------------------------------------ # Context menu @@ -1805,11 +1132,11 @@ class Edit(gui_base_original.Modifier): obj = App.getDocument(doc).getObject(self.overNode.get_obj_name()) idx = self.overNode.get_subelement_index() if action_label == "make sharp": - self.smoothBezPoint(obj, idx, 'Sharp') + edit_draft.smoothBezPoint(obj, idx, 'Sharp') elif action_label == "make tangent": - self.smoothBezPoint(obj, idx, 'Tangent') + edit_draft.smoothBezPoint(obj, idx, 'Tangent') elif action_label == "make symmetric": - self.smoothBezPoint(obj, idx, 'Symmetric') + edit_draft.smoothBezPoint(obj, idx, 'Symmetric') # addPoint and deletePoint menu elif action_label == "delete point": self.delPoint(self.event) diff --git a/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py b/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py new file mode 100644 index 0000000000..f8799f65ac --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py @@ -0,0 +1,227 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2019, 2020 Carlo Pavan * +# * * +# * 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 * +# * * +# *************************************************************************** +"""Provide the support functions to Draft_Edit for Arch objects.""" +## @package gui_edit_arch_objects +# \ingroup DRAFT +# \brief Provide the support functions to Draft_Edit for Arch objects + +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Carlo Pavan") +__url__ = "https://www.freecadweb.org" + + +import math +import FreeCAD as App +import DraftVecUtils + +from draftutils.translate import translate +import draftutils.utils as utils + + +# SKETCH: just if it's composed by a single segment----------------------- + +def getSketchPts(obj): + """Return the list of edipoints for the given single line sketch. + + (WallTrace) + 0 : startpoint + 1 : endpoint + """ + editpoints = [] + if obj.GeometryCount == 1: + editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,1))) + editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,2))) + return editpoints + else: + _wrn = translate("draft", "Sketch is too complex to edit: " + "it is suggested to use sketcher default editor") + App.Console.PrintWarning(_wrn + "\n") + return None + + +def updateSketch(obj, nodeIndex, v): + """Move a single line sketch vertex a certain displacement. + + (single segment sketch object, node index as Int, App.Vector) + move a single line sketch (WallTrace) vertex according to a given App.Vector + 0 : startpoint + 1 : endpoint + """ + if nodeIndex == 0: + obj.movePoint(0,1,obj.getGlobalPlacement().inverse().multVec(v)) + elif nodeIndex == 1: + obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v)) + obj.recompute() + + +# WALL--------------------------------------------------------------------- + +def getWallPts(obj): + """Return the list of edipoints for the given Arch Wall object. + + 0 : height of the wall + 1-to end : base object editpoints, in place with the wall + """ + editpoints = [] + # height of the wall + editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height))) + # try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker) + if obj.Base: + # base points are added to self.trackers under wall-name key + basepoints = [] + if utils.get_type(obj.Base) in ["Wire","Circle","Rectangle", + "Polygon", "Sketch"]: + pass # TODO: make it work again + #basepoints = self.getEditPoints(obj.Base) + #for point in basepoints: + # editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? + return editpoints + + +def updateWallTrackers(obj): + """Update self.trackers[obj.Name][0] to match with given object.""" + pass + + +def updateWall(obj, nodeIndex, v): + if nodeIndex == 0: + delta= obj.getGlobalPlacement().inverse().multVec(v) + vz = DraftVecUtils.project(delta, App.Vector(0, 0, 1)) + if vz.Length > 0: + obj.Height = vz.Length + elif nodeIndex > 0: + if obj.Base: + if utils.get_type(obj.Base) in ["Wire", "Circle", "Rectangle", + "Polygon", "Sketch"]: + pass #TODO: make it work again + #self.update(obj.Base, nodeIndex - 1, + # obj.Placement.inverse().multVec(v)) + obj.recompute() + + +# WINDOW------------------------------------------------------------------- + +def getWindowPts(obj): + editpoints = [] + pos = obj.Base.Placement.Base + h = float(obj.Height) + pos.z + normal = obj.Normal + angle = normal.getAngle(App.Vector(1, 0, 0)) + editpoints.append(pos) + editpoints.append(App.Vector(pos.x + float(obj.Width) * math.cos(angle-math.pi / 2.0), + pos.y + float(obj.Width) * math.sin(angle-math.pi / 2.0), + pos.z)) + editpoints.append(App.Vector(pos.x, pos.y, h)) + return editpoints + + +def updateWindow(obj, nodeIndex, v): + pos = obj.Base.Placement.Base + if nodeIndex == 0: + obj.Base.Placement.Base = v + obj.Base.recompute() + if nodeIndex == 1: + obj.Width = pos.sub(v).Length + obj.Base.recompute() + if nodeIndex == 2: + obj.Height = pos.sub(v).Length + obj.Base.recompute() + for obj in obj.Hosts: + obj.recompute() + obj.recompute() + + +# STRUCTURE---------------------------------------------------------------- + +def getStructurePts(obj): + if obj.Nodes: + editpoints = [] + # TODO: make it work again + #self.originalDisplayMode = obj.ViewObject.DisplayMode + #self.originalPoints = obj.ViewObject.NodeSize + #self.originalNodes = obj.ViewObject.ShowNodes + #self.obj.ViewObject.DisplayMode = "Wireframe" + #self.obj.ViewObject.NodeSize = 1 + ## self.obj.ViewObject.ShowNodes = True + #for p in obj.Nodes: + # if self.pl: + # p = self.pl.multVec(p) + # editpoints.append(p) + #return editpoints + else: + return None + + +def updateStructure(obj, nodeIndex, v): + nodes = obj.Nodes + nodes[nodeIndex] = obj.Placement.inverse().multVec(v) + obj.Nodes = nodes + + +# SPACE-------------------------------------------------------------------- + +def getSpacePts(obj): + try: + editpoints = [] + editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject)) + return editpoints + except: + pass + + +def updateSpace(obj, nodeIndex, v): + if nodeIndex == 0: + obj.ViewObject.TextPosition = v + + +# PANELS------------------------------------------------------------------- + +def getPanelCutPts(obj): + editpoints = [] + if obj.TagPosition.Length == 0: + pos = obj.Shape.BoundBox.Center + else: + pos = obj.Placement.multVec(obj.TagPosition) + editpoints.append(pos) + return editpoints + + +def updatePanelCut(obj, nodeIndex, v): + if nodeIndex == 0: + obj.TagPosition = obj.Placement.inverse().multVec(v) + + +def getPanelSheetPts(obj): + editpoints = [] + editpoints.append(obj.Placement.multVec(obj.TagPosition)) + for o in obj.Group: + editpoints.append(obj.Placement.multVec(o.Placement.Base)) + return editpoints + + +def updatePanelSheet(obj, nodeIndex, v): + if nodeIndex == 0: + obj.TagPosition = obj.Placement.inverse().multVec(v) + else: + obj.Group[nodeIndex-1].Placement.Base = obj.Placement.inverse().multVec(v) diff --git a/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py b/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py new file mode 100644 index 0000000000..af5442b138 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py @@ -0,0 +1,482 @@ +# *************************************************************************** +# * Copyright (c) 2010 Yorik van Havre * +# * Copyright (c) 2010 Ken Cline * +# * Copyright (c) 2020 Carlo Pavan * +# * * +# * 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 * +# * * +# *************************************************************************** +"""Provide the Draft_Edit command used by the Draft workbench.""" +## @package gui_edit_draft_objects +# \ingroup DRAFT +# \brief Provide the Draft_Edit command used by the Draft workbench + +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Carlo Pavan") +__url__ = "https://www.freecadweb.org" + + +import math +import FreeCAD as App +import DraftVecUtils + +from draftutils.translate import translate +import draftutils.utils as utils + + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve +# ------------------------------------------------------------------------- + +def getWirePts(obj): + editpoints = [] + for p in obj.Points: + p = obj.getGlobalPlacement().multVec(p) + editpoints.append(p) + return editpoints + +def updateWire(obj, nodeIndex, v): #TODO: Fix it + pts = obj.Points + editPnt = obj.getGlobalPlacement().inverse().multVec(v) + # DNC: allows to close the curve by placing ends close to each other + tol = 0.001 + if ( ( nodeIndex == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( + nodeIndex == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): + obj.Closed = True + # DNC: fix error message if edited point coincides with one of the existing points + if ( editPnt in pts ) == True: # checks if point enter is equal to other, this could cause a OCC problem + App.Console.PrintMessage(translate("draft", + "This object does not support possible " + "coincident points, please try again.") + + "\n") + if utils.get_type(obj) in ["BezCurve"]: # TODO: Remove code to recompute trackers + self.resetTrackers(obj) + else: + self.trackers[obj.Name][nodeIndex].set(obj.getGlobalPlacement(). + multVec(obj.Points[nodeIndex])) + return + if utils.get_type(obj) in ["BezCurve"]: + pts = recomputePointsBezier(obj,pts,nodeIndex,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 + print(v) + v.projectToPlane(point_on_plane, normal) + print(v) + editPnt = obj.getGlobalPlacement().inverse().multVec(v) + pts[nodeIndex] = editPnt + obj.Points = pts + + +def recomputePointsBezier(obj, pts, idx, v, + degree, moveTrackers=True): + """ + (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 + """ + editPnt = v + # DNC: allows to close the curve by placing ends close to each other + tol = 0.001 + if ( ( idx == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( + idx == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): + obj.Closed = True + # DNC: fix error message if edited point coincides with one of the existing points + #if ( editPnt 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] + editPnt - pts[knotidx] + if moveTrackers: + self.trackers[obj.Name][idx-1].set(pts[idx-1]) # TODO: Remove code to recompute trackers + if idx < len(pts)-1: #move right pole + pts[idx+1] = pts[idx+1] + editPnt - 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] + editPnt -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], + editPnt,pts[changep]) + if moveTrackers: + self.trackers[obj.Name][changep].set(pts[changep]) + elif cont == 2: #symmetric + pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt) + 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 resetTrackersBezier(obj): + # in future move tracker definition to DraftTrackers + from pivy import coin + knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp + coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent + coin.SoMarkerSet.HOURGLASS_FILLED_9_9) #symmetric + polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9 #pole + self.trackers[obj.Name] = [] + cont = obj.Continuity + firstknotcont = cont[-1] if (obj.Closed and cont) else 0 + pointswithmarkers = [(obj.Shape.Edges[0].Curve. + getPole(1),knotmarkers[firstknotcont])] + for edgeindex, edge in enumerate(obj.Shape.Edges): + poles = edge.Curve.getPoles() + pointswithmarkers.extend([(point,polemarker) for point in poles[1:-1]]) + if not obj.Closed or len(obj.Shape.Edges) > edgeindex +1: + knotmarkeri = cont[edgeindex] if len(cont) > edgeindex else 0 + pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri])) + for index, pwm in enumerate(pointswithmarkers): + p, marker = pwm + # if self.pl: p = self.pl.multVec(p) + self.trackers[obj.Name].append(trackers.editTracker(p,obj.Name, + index,obj.ViewObject.LineColor,marker=marker)) + +def smoothBezPoint(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 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", + "Can't 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 BezCurve can't 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 + + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Rectangle +# ------------------------------------------------------------------------- + +def getRectanglePts(obj): + """Return the list of edipoints for the given Draft Rectangle. + + 0 : Placement.Base + 1 : Length + 2 : Height + """ + editpoints = [] + editpoints.append(obj.getGlobalPlacement().Base) + editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0))) + editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0))) + return editpoints + + +def updateRectangle(obj, nodeIndex, v): + delta = obj.getGlobalPlacement().inverse().multVec(v) + if nodeIndex == 0: + # p = obj.getGlobalPlacement() + # p.move(delta) + obj.Placement.move(delta) + elif nodeIndex == 1: + obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length + elif nodeIndex == 2: + obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length + + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented) +# ------------------------------------------------------------------------- + +def setEllipsePts(obj): + return + +def updateEllipse(obj, nodeIndex, v): + return + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Circle/Arc +# ------------------------------------------------------------------------- + +def getCirclePts(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(obj.getGlobalPlacement().Base) + if obj.FirstAngle == obj.LastAngle: + # obj is a circle + editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Radius,0,0))) + else: + # obj is an arc + editpoints.append(getArcStart(obj, global_placement=True))#First endpoint + editpoints.append(getArcEnd(obj, global_placement=True))#Second endpoint + editpoints.append(getArcMid(obj, global_placement=True))#Midpoint + return editpoints + + +def updateCircle(obj, nodeIndex, v, alt_edit_mode=0): + delta = obj.getGlobalPlacement().inverse().multVec(v) + local_v = obj.Placement.multVec(delta) + + if obj.FirstAngle == obj.LastAngle: + # object is a circle + if nodeIndex == 0: + obj.Placement.Base = local_v + elif nodeIndex == 1: + obj.Radius = delta.Length + + else: + # obj is an arc + if alt_edit_mode == 0: + # edit arc by 3 points + import Part + if nodeIndex == 0: + # center point + p1 = getArcStart(obj) + p2 = getArcEnd(obj) + p0 = DraftVecUtils.project(delta, 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: + if nodeIndex == 1: # first point + p1 = v + p2 = getArcMid(obj, global_placement=True) + p3 = getArcEnd(obj, global_placement=True) + elif nodeIndex == 3: # midpoint + p1 = getArcStart(obj, global_placement=True) + p2 = v + p3 = getArcEnd(obj, global_placement=True) + elif nodeIndex == 2: # second point + p1 = getArcStart(obj, global_placement=True) + p2 = getArcMid(obj, global_placement=True) + p3 = v + arc=Part.ArcOfCircle(p1,p2,p3) + obj.Placement.Base = obj.Placement.multVec(obj.getGlobalPlacement().inverse().multVec(arc.Location)) + obj.Radius = arc.Radius + delta = obj.Placement.inverse().multVec(p1) + obj.FirstAngle = math.degrees(math.atan2(delta[1],delta[0])) + delta = obj.Placement.inverse().multVec(p3) + obj.LastAngle = math.degrees(math.atan2(delta[1],delta[0])) + + elif alt_edit_mode == 1: + # edit arc by center radius FirstAngle LastAngle + if nodeIndex == 0: + obj.Placement.Base = local_v + else: + dangle = math.degrees(math.atan2(delta[1],delta[0])) + if nodeIndex == 1: + obj.FirstAngle = dangle + elif nodeIndex == 2: + obj.LastAngle = dangle + elif nodeIndex == 3: + obj.Radius = delta.Length + + obj.recompute() + + +def getArcStart(obj, global_placement=False):#Returns object midpoint + if utils.get_type(obj) == "Circle": + return pointOnCircle(obj, obj.FirstAngle, global_placement) + + +def getArcEnd(obj, global_placement=False):#Returns object midpoint + if utils.get_type(obj) == "Circle": + return pointOnCircle(obj, obj.LastAngle, global_placement) + + +def getArcMid(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 pointOnCircle(obj, midAngle, global_placement) + + +def pointOnCircle(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 == True: + p = obj.getGlobalPlacement().multVec(p) + return p + return None + + +def arcInvert(obj): + obj.FirstAngle, obj.LastAngle = obj.LastAngle, obj.FirstAngle + obj.recompute() + + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon) +# ------------------------------------------------------------------------- + +def getPolygonPts(obj): + editpoints = [] + editpoints.append(obj.Placement.Base) + editpoints.append(obj.Shape.Vertexes[0].Point) + return editpoints + + +def updatePolygon(obj, nodeIndex, v): + delta = v.sub(obj.Placement.Base) + if nodeIndex == 0: + p = obj.Placement + p.move(delta) + obj.Placement = p + elif nodeIndex == 1: + if obj.DrawMode == 'inscribed': + obj.Radius = delta.Length + else: + halfangle = ((math.pi*2)/obj.FacesNumber)/2 + rad = math.cos(halfangle)*delta.Length + obj.Radius = rad + obj.recompute() + + +# ------------------------------------------------------------------------- +# EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable) +# ------------------------------------------------------------------------- + +def getDimensionPts(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 updateDimension(obj, nodeIndex, v): + if nodeIndex == 0: + obj.Start = v + elif nodeIndex == 1: + obj.End = v + elif nodeIndex == 2: + obj.Dimline = v + elif nodeIndex == 3: + obj.ViewObject.TextPosition = v + + diff --git a/src/Mod/Draft/draftguitools/gui_edit_part_objects.py b/src/Mod/Draft/draftguitools/gui_edit_part_objects.py new file mode 100644 index 0000000000..49bb049e44 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_edit_part_objects.py @@ -0,0 +1,79 @@ +# *************************************************************************** +# * Copyright (c) 2019 Carlo Pavan * +# * * +# * 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 * +# * * +# *************************************************************************** +"""Provide the support functions to Draft_Edit for Part objects.""" +## @package gui_edit_part_objects +# \ingroup DRAFT +# \brief Provide the support functions to Draft_Edit for Part objects + +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Carlo Pavan") +__url__ = "https://www.freecadweb.org" + + +import FreeCAD as App +import DraftVecUtils + + +# PART::LINE-------------------------------------------------------------- + +def getPartLinePts(obj): + editpoints = [] + editpoints.append(obj.Placement.multVec(App.Vector(obj.X1,obj.Y1,obj.Z1))) + editpoints.append(obj.Placement.pl.multVec(App.Vector(obj.X2,obj.Y2,obj.Z2))) + return editpoints + + +def updatePartLine(obj, nodeIndex, v): + pt=obj.Placement.inverse().multVec(v) + if nodeIndex == 0: + obj.X1 = pt.x + obj.Y1 = pt.y + obj.Z1 = pt.z + elif nodeIndex == 1: + obj.X2 = pt.x + obj.Y2 = pt.y + obj.Z2 = pt.z + +# PART::BOX--------------------------------------------------------------- + +def getPartBoxPts(self, obj): + editpoints = [] + editpoints.append(obj.Placement.Base) + editpoints.append(obj.Placement.multVec(App.Vector(obj.Length, 0, 0))) + editpoints.append(obj.Placement.multVec(App.Vector(0, obj.Width, 0))) + editpoints.append(obj.Placement.multVec(App.Vector(0, 0, obj.Height))) + return editpoints + + +def updatePartBox(self, obj, nodeIndex, v): + delta = obj.Placement.inverse().multVec(v) + if nodeIndex == 0: + obj.Placement.Base = v + elif nodeIndex == 1: + _vector = DraftVecUtils.project(delta, App.Vector(1, 0, 0)) + obj.Length = _vector.Length + elif nodeIndex == 2: + _vector = DraftVecUtils.project(delta, App.Vector(0, 1, 0)) + obj.Width = _vector.Length + elif nodeIndex == 3: + _vector = DraftVecUtils.project(delta, App.Vector(0, 0, 1)) + obj.Height = _vector.Length