From 28c00f8383a9daf8b53d2dce294b09e0714a84f8 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sun, 16 Aug 2020 00:26:29 +0200 Subject: [PATCH] Draft: provide the possibility to implement support for Edit in objs As suggested by @Vanuan. and already implemented in new experimental BIM Wall: https://github.com/yorikvanhavre/BIM_Workbench/commit/1084a4b0a95c2f73fc852d7f91142e16720d4023 Draft: updated Draft Edit thanks to the suggestions by @Vanuan Draft: further bugfixing and improvements in Draft_Edit Draft: moved Draft_Edit preview code into respective GuiTools --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/draftguitools/gui_edit.py | 476 +++----- .../draftguitools/gui_edit_arch_objects.py | 322 ++--- .../draftguitools/gui_edit_base_object.py | 90 ++ .../draftguitools/gui_edit_draft_objects.py | 1070 ++++++++++------- .../draftguitools/gui_edit_part_objects.py | 224 ++-- .../gui_edit_sketcher_objects.py | 80 +- src/Mod/Draft/draftutils/utils.py | 36 +- 8 files changed, 1218 insertions(+), 1081 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_edit_base_object.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index c051f0f2cc..ea3232f089 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -251,6 +251,7 @@ SET(Draft_GUI_tools draftguitools/gui_snaps.py draftguitools/gui_snapper.py draftguitools/gui_trackers.py + draftguitools/gui_edit_base_object.py draftguitools/gui_edit_draft_objects.py draftguitools/gui_edit_arch_objects.py draftguitools/gui_edit_part_objects.py diff --git a/src/Mod/Draft/draftguitools/gui_edit.py b/src/Mod/Draft/draftguitools/gui_edit.py index affff3a704..722e90cec7 100644 --- a/src/Mod/Draft/draftguitools/gui_edit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -42,9 +42,9 @@ import FreeCADGui as Gui import DraftVecUtils import draftutils.utils as utils import draftutils.gui_utils as gui_utils +import draftguitools.gui_trackers as trackers import draftguitools.gui_base_original as gui_base_original import draftguitools.gui_tool_utils as gui_tool_utils -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 @@ -226,7 +226,8 @@ class Edit(gui_base_original.Modifier): self._mouseMovedCB = None self._mousePressedCB = None - # this are used to edit structure objects, it's a bit buggy i think + # this stores the DisplayMode of the object to restore it after editing + # only used by Arch Structure self.objs_formats = {} # settings @@ -234,16 +235,38 @@ class Edit(gui_base_original.Modifier): self.maxObjects = param.GetInt("DraftEditMaxObjects", 5) self.pick_radius = param.GetInt("DraftEditPickRadius", 20) - self.alt_edit_mode = 0 # alternative edit mode for objects + self.alt_edit_mode = 0 # default edit mode for objects # preview self.ghost = None - #list of supported objects - self.supportedObjs = edit_draft.get_supported_draft_objects() + \ - edit_arch.get_supported_arch_objects() - self.supportedCppObjs = edit_part.get_supported_part_objects() + \ - edit_sketcher.get_supported_sketcher_objects() + # setup gui_tools for every supported object + self.gui_tools_repository = GuiToolsRepository() + + self.gui_tools_repository.add('Wire', edit_draft.DraftWireGuiTools()) + self.gui_tools_repository.add('BSpline', edit_draft.DraftBSplineGuiTools()) + self.gui_tools_repository.add('BezCurve', edit_draft.DraftBezCurveGuiTools()) + self.gui_tools_repository.add('Circle', edit_draft.DraftCircleGuiTools()) + self.gui_tools_repository.add('Rectangle', edit_draft.DraftRectangleGuiTools()) + self.gui_tools_repository.add('Polygon', edit_draft.DraftPolygonGuiTools()) + self.gui_tools_repository.add('Ellipse', edit_draft.DraftEllipseGuiTools()) + self.gui_tools_repository.add('Dimension', edit_draft.DraftDimensionGuiTools()) # Backward compatibility + self.gui_tools_repository.add('LinearDimension', edit_draft.DraftDimensionGuiTools()) + + self.gui_tools_repository.add('Wall', edit_arch.ArchWallGuiTools()) + self.gui_tools_repository.add('Window', edit_arch.ArchWindowGuiTools()) + self.gui_tools_repository.add('Structure', edit_arch.ArchStructureGuiTools()) + self.gui_tools_repository.add('Space', edit_arch.ArchSpaceGuiTools()) + self.gui_tools_repository.add('PanelCut', edit_arch.ArchPanelCutGuiTools()) + self.gui_tools_repository.add('PanelSheet', edit_arch.ArchPanelSheetGuiTools()) + + self.gui_tools_repository.add('Part::Line', edit_part.PartLineGuiTools()) + self.gui_tools_repository.add('Part::Box', edit_part.PartBoxGuiTools()) + self.gui_tools_repository.add('Part::Cylinder', edit_part.PartCylinderGuiTools()) + self.gui_tools_repository.add('Part::Cone', edit_part.PartConeGuiTools()) + self.gui_tools_repository.add('Part::Sphere', edit_part.PartSphereGuiTools()) + + self.gui_tools_repository.add('Sketcher::SketchObject', edit_sketcher.SketcherSketchObjectGuiTools()) def GetResources(self): @@ -422,9 +445,6 @@ class Edit(gui_base_original.Modifier): self.finish(closed=True) if key == 101: # "e" self.display_tracker_menu(event) - if key == 105: # "i" - if utils.get_type(self.obj) == "Circle": - 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) @@ -489,8 +509,10 @@ class Edit(gui_base_original.Modifier): self.ui.isRelative.show() self.editing = ep self.trackers[self.obj.Name][self.editing].off() + self.finalizeGhost() - self.ghost = self.initGhost(self.obj) + self.initGhost(self.obj) + self.node.append(self.trackers[self.obj.Name][self.editing].get()) Gui.Snapper.setSelectMode(False) self.hideTrackers() @@ -505,7 +527,7 @@ class Edit(gui_base_original.Modifier): self.trackers[self.obj.Name][self.editing].set(snappedPos) self.ui.displayPoint(snappedPos, self.node[-1]) if self.ghost: - self.updateGhost(obj=self.obj, idx=self.editing, pt=snappedPos) + self.updateGhost(obj=self.obj, node_idx=self.editing, v=snappedPos) def endEditing(self, obj, nodeIndex, v=None): """Terminate editing and start object updating process.""" @@ -630,90 +652,21 @@ class Edit(gui_base_original.Modifier): # ------------------------------------------------------------------------- def initGhost(self, obj): - """Initialize preview ghost.""" - if utils.get_type(obj) == "Wire": - return trackers.wireTracker(obj.Shape) - elif utils.get_type(obj) == "BSpline": - return trackers.bsplineTracker() - elif utils.get_type(obj) == "BezCurve": - return trackers.bezcurveTracker() - elif utils.get_type(obj) == "Circle": - return trackers.arcTracker() + self.current_editing_object_gui_tools = self.get_obj_gui_tools(obj) + if self.current_editing_object_gui_tools: + self.ghost = self.current_editing_object_gui_tools.init_preview_object(obj) + + def updateGhost(self, obj, node_idx, v): + self.ghost.on() + + if self.current_editing_object_gui_tools: + self.current_editing_object_gui_tools.update_preview_object(self, obj, node_idx, v) - def updateGhost(self, obj, idx, pt): - if utils.get_type(obj) in ["Wire"]: - self.ghost.on() - pointList = self.globalize_vectors(obj, obj.Points) - pointList[idx] = pt - if obj.Closed: - pointList.append(pointList[0]) - self.ghost.updateFromPointlist(pointList) - elif utils.get_type(obj) == "BSpline": - self.ghost.on() - pointList = self.globalize_vectors(obj, obj.Points) - pointList[idx] = pt - if obj.Closed: - pointList.append(pointList[0]) - self.ghost.update(pointList) - elif utils.get_type(obj) == "BezCurve": - self.ghost.on() - plist = self.globalize_vectors(obj, obj.Points) - pointList = edit_draft.recomputePointsBezier(obj,plist,idx,pt,obj.Degree,moveTrackers=False) - self.ghost.update(pointList,obj.Degree) - elif utils.get_type(obj) == "Circle": - self.ghost.on() - self.ghost.setCenter(obj.getGlobalPlacement().Base) - self.ghost.setRadius(obj.Radius) - if obj.FirstAngle == obj.LastAngle: - # obj is a circle - self.ghost.circle = True - if self.editing == 0: - self.ghost.setCenter(pt) - elif self.editing == 1: - radius = pt.sub(obj.getGlobalPlacement().Base).Length - self.ghost.setRadius(radius) - else: - if self.alt_edit_mode == 0: - # edit by 3 points - if self.editing == 0: - # center point - p1 = self.relativize_vector(obj, obj.Shape.Vertexes[0].Point) - p2 = self.relativize_vector(obj, obj.Shape.Vertexes[1].Point) - p0 = DraftVecUtils.project(self.relativize_vector(obj, pt), - self.relativize_vector(obj, (edit_draft.getArcMid(obj, global_placement=True)))) - self.ghost.autoinvert=False - self.ghost.setRadius(p1.sub(p0).Length) - self.ghost.setStartPoint(obj.Shape.Vertexes[1].Point) - self.ghost.setEndPoint(obj.Shape.Vertexes[0].Point) - self.ghost.setCenter(self.globalize_vector(obj, p0)) - return - else: - 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: - p2=pt - elif self.editing == 2: - p3=pt - self.ghost.setBy3Points(p1,p2,p3) - elif self.alt_edit_mode == 1: - # edit by center radius angles - self.ghost.setStartAngle(math.radians(obj.FirstAngle)) - self.ghost.setEndAngle(math.radians(obj.LastAngle)) - if self.editing == 0: - self.ghost.setCenter(pt) - elif self.editing == 1: - self.ghost.setStartPoint(pt) - elif self.editing == 2: - self.ghost.setEndPoint(pt) - elif self.editing == 3: - self.ghost.setRadius(self.relativize_vector(obj, pt).Length) gui_tool_utils.redraw_3d_view() def finalizeGhost(self): try: + self.current_editing_object_gui_tools = None self.ghost.finalize() self.ghost = None except: @@ -763,11 +716,11 @@ class Edit(gui_base_original.Modifier): for index, point in enumerate(obj.Points): if index == edgeIndex: - newPoints.append(self.relativize_vector(obj, newPoint)) + newPoints.append(self.localize_vectors(obj, newPoint)) newPoints.append(point) if obj.Closed and edgeIndex == len(obj.Points): # last segment when object is closed - newPoints.append(self.relativize_vector(obj, newPoint)) + newPoints.append(self.localize_vectors(obj, newPoint)) obj.Points = newPoints def addPointToCurve(self, point, obj, info=None): @@ -815,45 +768,13 @@ class Edit(gui_base_original.Modifier): uPoints.append(curve.parameter(p)) for i in range(len(uPoints) - 1): if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ): - pts.insert(i + 1, self.relativize_vector(obj, point)) + pts.insert(i + 1, self.localize_vectors(obj, point)) break # DNC: fix: add points to last segment if curve is closed if obj.Closed and (uNewPoint > uPoints[-1]): - pts.append(self.relativize_vector(obj, point)) + pts.append(self.localize_vectors(obj, point)) obj.Points = pts - def delPoint(self, event): - pos = event.getPosition() - node = self.getEditNode(pos) - ep = self.getEditNodeIndex(node) - - if ep is None: - _msg = translate("draft", "Node not found") - App.Console.PrintWarning(_msg + "\n") - return - - doc = App.getDocument(str(node.documentName.getValue())) - obj = doc.getObject(str(node.objectName.getValue())) - if obj is None: - return - if utils.get_type(obj) not in ["Wire", "BSpline", "BezCurve"]: - return - if len(obj.Points) <= 2: - _msg = translate("draft", "Active object must have more than two points/nodes") - App.Console.PrintWarning(_msg + "\n") - return - - pts = obj.Points - pts.pop(ep) - obj.Points = pts - if utils.get_type(obj) == "BezCurve": - obj.Proxy.resetcontinuity(obj) - obj.recompute() - - # don't do tan/sym on DWire/BSpline! - self.resetTrackers(obj) - - # ------------------------------------------------------------------------ # DRAFT EDIT Context menu # ------------------------------------------------------------------------ @@ -862,44 +783,34 @@ class Edit(gui_base_original.Modifier): self.tracker_menu = QtGui.QMenu() self.event = event actions = None + if self.overNode: # if user is over a node doc = self.overNode.get_doc_name() obj = App.getDocument(doc).getObject(self.overNode.get_obj_name()) ep = self.overNode.get_subelement_index() - if utils.get_type(obj) in ["Line", "Wire", "BSpline"]: - actions = ["delete point"] - elif utils.get_type(obj) in ["BezCurve"]: - actions = ["make sharp", "make tangent", - "make symmetric", "delete point"] - elif utils.get_type(obj) in ["Circle"]: - if obj.FirstAngle != obj.LastAngle: - if ep == 0: # user is over arc start point - actions = ["move arc"] - elif ep == 1: # user is over arc start point - actions = ["set first angle"] - elif ep == 2: # user is over arc end point - actions = ["set last angle"] - elif ep == 3: # user is over arc mid point - actions = ["set radius"] - elif utils.get_type(obj) == "Arch_Wall": - if ep == 0 or ep == 1: # user is a wall endpoint - actions = ["reset end", "align"] - else: - return + + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj_gui_tools: + actions = obj_gui_tools.get_edit_point_context_menu(obj, ep) + else: - # if user is over an edited object + # try if user is over an edited object pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) if utils.get_type(obj) in ["Line", "Wire", "BSpline", "BezCurve"]: actions = ["add point"] elif utils.get_type(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle: actions = ["invert arc"] + if actions is None: return + for a in actions: self.tracker_menu.addAction(a) + self.tracker_menu.popup(Gui.getMainWindow().cursor().pos()) + QtCore.QObject.connect(self.tracker_menu, QtCore.SIGNAL("triggered(QAction *)"), self.evaluate_menu_action) @@ -908,48 +819,26 @@ class Edit(gui_base_original.Modifier): def evaluate_menu_action(self, labelname): action_label = str(labelname.text()) + doc = None + obj = None + idx = None + if self.overNode: doc = self.overNode.get_doc_name() obj = App.getDocument(doc).getObject(self.overNode.get_obj_name()) idx = self.overNode.get_subelement_index() - # addPoint and deletePoint menu - - if action_label == "delete point": - self.delPoint(self.event) + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj and obj_gui_tools: + actions = obj_gui_tools.evaluate_context_menu_action(self, obj, idx, action_label) elif action_label == "add point": self.addPoint(self.event) - # Bezier curve menu - elif action_label in ["make sharp", "make tangent", "make symmetric"]: - if action_label == "make sharp": - edit_draft.smoothBezPoint(obj, idx, 'Sharp') - elif action_label == "make tangent": - edit_draft.smoothBezPoint(obj, idx, 'Tangent') - elif action_label == "make symmetric": - edit_draft.smoothBezPoint(obj, idx, 'Symmetric') - self.resetTrackers(obj) - - # arc tools - elif action_label in ("move arc", "set radius", - "set first angle", "set last angle"): - self.alt_edit_mode = 1 - self.startEditing(self.event) - elif action_label == "invert arc": pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) - edit_draft.arcInvert(obj) - - # experimental wall tools - elif action_label in ["reset end", "align"]: - if action_label == "reset end": - obj.Proxy.reset_end(obj, idx) - obj.recompute() - if action_label == "align": - self.alt_edit_mode = 1 - self.startEditing(self.event) + obj_gui_tools.arcInvert(obj) del self.event @@ -965,89 +854,20 @@ class Edit(gui_base_original.Modifier): """Return a list of App.Vectors according to the given object edit nodes. """ eps = None - objectType = utils.get_type(obj) - is_global = False # set to true if the editpoints are returned in global coordinates system - - if objectType in ["Wire", "BSpline"]: - eps = edit_draft.getWirePts(obj) - - elif objectType == "BezCurve": - return - - elif objectType == "Circle": - eps = edit_draft.getCirclePts(obj) - - elif objectType == "Rectangle": - eps = edit_draft.getRectanglePts(obj) - - elif objectType == "Polygon": - eps = edit_draft.getPolygonPts(obj) - - elif objectType == "Ellipse": - eps = edit_draft.getEllipsePts(obj) - - elif objectType in ("Dimension","LinearDimension"): - eps = edit_draft.getDimensionPts(obj) - - elif objectType == "Wall": - eps = self.globalize_vectors(obj, edit_arch.getWallPts(obj)) - if obj.Base and utils.get_type(obj.Base) in ["Wire","Circle", - "Rectangle", "Polygon", "Sketch"]: - basepoints = self.getEditPoints(obj.Base) - for point in basepoints: - eps.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? - return eps - - elif objectType == "Arch_Wall": - eps = edit_arch.getArchWallPts(obj) - is_global = True - - elif objectType == "Window": - eps = edit_arch.getWindowPts(obj) - - elif objectType == "Space": - eps = edit_arch.getSpacePts(obj) - - elif objectType == "Structure": - eps = edit_arch.getStructurePts(obj) - - elif objectType == "PanelCut": - eps = edit_arch.getPanelCutPts(obj) - - elif objectType == "PanelSheet": - eps = edit_arch.getPanelSheetPts(obj) - - elif objectType == "Part::Line" and obj.TypeId == "Part::Line": - eps = edit_part.getPartLinePts(obj) - - elif objectType == "Part" and obj.TypeId == "Part::Box": - eps = edit_part.getPartBoxPts(obj) - - elif objectType == "Part" and obj.TypeId == "Part::Cylinder": - eps = edit_part.getPartCylinderPts(obj) - - elif objectType == "Part" and obj.TypeId == "Part::Cone": - eps = edit_part.getPartConePts(obj) - - elif objectType == "Part" and obj.TypeId == "Part::Sphere": - eps = edit_part.getPartSpherePts(obj) - - elif objectType == "Sketch": - eps = edit_sketcher.getSketchPts(obj) - - if not is_global: - eps = self.globalize_vectors(obj, eps) + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj_gui_tools: + eps = obj_gui_tools.get_edit_points(obj) if eps: - return eps + return self.globalize_vectors(obj, eps) else: return None def update(self, obj, nodeIndex, v): """Apply the App.Vector to the modified point and update obj.""" - v = self.relativize_vector(obj, v) + v = self.localize_vectors(obj, v) App.ActiveDocument.openTransaction("Edit") self.update_object(obj, nodeIndex, v) App.ActiveDocument.commitTransaction() @@ -1059,72 +879,11 @@ class Edit(gui_base_original.Modifier): def update_object(self, obj, nodeIndex, v): - objectType = utils.get_type(obj) - if objectType in ["Wire", "BSpline"]: - edit_draft.updateWire(obj, nodeIndex, v) - - elif objectType == "BezCurve": - edit_draft.updateBezCurve(obj, nodeIndex, v) - - elif objectType == "Circle": - edit_draft.updateCircle(obj, nodeIndex, v, self.alt_edit_mode) - - elif objectType == "Rectangle": - edit_draft.updateRectangle(obj, nodeIndex, v) - - elif objectType == "Polygon": - edit_draft.updatePolygon(obj, nodeIndex, v) - - elif objectType == "Ellipse": - edit_draft.updateEllipse(obj, nodeIndex, v) - - elif objectType in ("Dimension","LinearDimension"): - edit_draft.updateDimension(obj, nodeIndex, v) - - elif objectType == "Sketch": - edit_sketcher.updateSketch(obj, nodeIndex, v) - - elif objectType == "Wall": - if nodeIndex == 0: - edit_arch.updateWall(obj, nodeIndex, v) - elif nodeIndex > 0: - if obj.Base: - if utils.get_type(obj.Base) in ["Wire", "Circle", "Rectangle", - "Polygon", "Sketch"]: - self.update(obj.Base, nodeIndex - 1, v) - - elif objectType == "Arch_Wall": - eps = edit_arch.updateArchWall(obj, nodeIndex, v, self.alt_edit_mode) - - elif objectType == "Window": - edit_arch.updateWindow(obj, nodeIndex, v) - - elif objectType == "Space": - edit_arch.updateSpace(obj, nodeIndex, v) - - elif objectType == "Structure": - edit_arch.updateStructure(obj, nodeIndex, v) - - elif objectType == "PanelCut": - edit_arch.updatePanelCut(obj, nodeIndex, v) - - elif objectType == "PanelSheet": - edit_arch.updatePanelSheet(obj, nodeIndex, v) - - elif objectType == "Part::Line" and obj.TypeId == "Part::Line": - edit_part.updatePartLine(obj, nodeIndex, v) - - elif objectType == "Part" and obj.TypeId == "Part::Box": - edit_part.updatePartBox(obj, nodeIndex, v) - - elif objectType == "Part" and obj.TypeId == "Part::Cylinder": - edit_part.updatePartCylinder(obj, nodeIndex, v) - - elif objectType == "Part" and obj.TypeId == "Part::Cone": - edit_part.updatePartCone(obj, nodeIndex, v) - - elif objectType == "Part" and obj.TypeId == "Part::Sphere": - edit_part.updatePartSphere(obj, nodeIndex, v) + """Update the object according to the given modified editpoint. + """ + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj_gui_tools: + eps = obj_gui_tools.update_object_from_edit_points(obj, nodeIndex, v, self.alt_edit_mode) obj.recompute() @@ -1133,6 +892,33 @@ class Edit(gui_base_original.Modifier): # UTILS # ------------------------------------------------------------------------- + def has_obj_gui_tools(self, obj): + """ Check if the object has the GuiTools to provide information to edit it. + """ + if (hasattr(obj, 'obj_gui_tools') or + (hasattr(obj, 'Proxy') and hasattr(obj.Proxy, 'obj_gui_tools')) or + (utils.get_type(obj) in self.gui_tools_repository.keys()) ): + return True + else: + return False + + + def get_obj_gui_tools(self, obj): + """ Retrieve the obj_gui_tools to support Draft Edit. + """ + try: + obj_gui_tools = obj.obj_gui_tools + except AttributeError: + try: + obj_gui_tools = obj.Proxy.obj_gui_tools + except AttributeError: + try: + obj_gui_tools = self.gui_tools_repository.get(utils.get_type(obj)) + except: + obj_gui_tools = None + return obj_gui_tools + + def getObjsFromSelection(self): """Evaluate selection and return a valid object to edit. @@ -1149,16 +935,13 @@ class Edit(gui_base_original.Modifier): _err = translate("draft", "Too many objects selected, max number set to: ") App.Console.PrintMessage(_err + str(self.maxObjects) + "\n") return None + for obj in selection: - if utils.get_type(obj) in self.supportedObjs: + if self.has_obj_gui_tools(obj): self.edited_objects.append(obj) - continue - elif utils.get_type(obj) in self.supportedCppObjs: - if obj.TypeId in self.supportedCppObjs: - self.edited_objects.append(obj) - continue - _wrn = translate("draft", ": this object is not editable") - App.Console.PrintWarning(obj.Name + _wrn + "\n") + else: + _wrn = translate("draft", ": this object is not editable") + App.Console.PrintWarning(obj.Name + _wrn + "\n") return self.edited_objects @@ -1166,19 +949,19 @@ class Edit(gui_base_original.Modifier): """Change objects style during editing mode. """ for obj in objs: - # TODO: Placeholder for changing the Selectable property of obj ViewProvide - if utils.get_type(obj) == "Structure": - self.objs_formats[obj.Name] = edit_arch.get_structure_format(obj) - edit_arch.set_structure_editing_format(obj) + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj_gui_tools: + self.objs_formats[obj.Name] = obj_gui_tools.get_object_style(obj) + obj_gui_tools.set_object_editing_style(obj) def deformat_objects_after_editing(self, objs): """Restore objects style during editing mode. """ for obj in objs: - # TODO: Placeholder for changing the Selectable property of obj ViewProvide - if utils.get_type(obj) == "Structure": - edit_arch.restore_structure_format(obj, self.objs_formats[obj.Name]) + obj_gui_tools = self.get_obj_gui_tools(obj) + if obj_gui_tools: + obj_gui_tools.restore_object_style(obj, self.objs_formats[obj.Name]) def get_selected_obj_at_position(self, pos): @@ -1211,15 +994,15 @@ class Edit(gui_base_original.Modifier): else: return point - def relativize_vectors(self, obj, pointList): + def localize_vectors(self, obj, pointList): """Return the given point list in the given object coordinate system.""" plist = [] for p in pointList: - point = self.relativize_vector(obj, p) + point = self.localize_vectors(obj, p) plist.append(point) return plist - def relativize_vector(self, obj, point): + def localize_vectors(self, obj, point): """Return the given point in the given object coordinate system.""" if hasattr(obj, "getGlobalPlacement"): return obj.getGlobalPlacement().inverse().multVec(point) @@ -1262,6 +1045,25 @@ class Edit(gui_base_original.Modifier): return None + +class GuiToolsRepository(): + """ This object provide a repository to collect all the specific objects + editing tools. + """ + def __init__(self): + self.obj_gui_tools = {} + + def get(self, obj_type): + return self.obj_gui_tools[obj_type] + + def add(self, type, gui_tools): + self.obj_gui_tools[type] = gui_tools + + def keys(self): + return self.obj_gui_tools.keys() + + + Gui.addCommand('Draft_Edit', Edit()) ## @} diff --git a/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py b/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py index 2cb615be60..8c6cd28f3c 100644 --- a/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py +++ b/src/Mod/Draft/draftguitools/gui_edit_arch_objects.py @@ -39,181 +39,199 @@ import DraftVecUtils from draftutils.translate import translate import draftutils.utils as utils - -def get_supported_arch_objects(): - return ["Arch_Wall", "Wall", "Window", "Structure", "Space", "PanelCut", "PanelSheet"] +from draftguitools.gui_edit_base_object import GuiTools -# WALL--------------------------------------------------------------------- -def getWallPts(obj): - """Return the list of edipoints for the given Arch Wall object. +class ArchWallGuiTools(GuiTools): - 0 : height of the wall - 1-to end : base object editpoints, in place with the wall - """ - editpoints = [] - # height of the wall - editpoints.append(App.Vector(0, 0, obj.Height)) - return editpoints + def __init__(self): + pass + def get_edit_points(self, obj): + """Return the list of edipoints for the given Arch Wall object. -def updateWall(obj, nodeIndex, v): - if nodeIndex == 0: - vz = DraftVecUtils.project(v, App.Vector(0, 0, 1)) - if vz.Length > 0: - obj.Height = vz.Length - obj.recompute() + 0 : height of the wall + 1-to end : base object editpoints, in place with the wall + """ -# Arch_Wall --------------------------------------------------------------------- -# support for new experimental Arch_Wall - -def getArchWallPts(obj): - """Return the list of edipoints for the given Arch Wall object. - """ - editpoints = [] - # height of the wall - editpoints.append(obj.Proxy.get_first_point(obj)) - editpoints.append(obj.Proxy.get_last_point(obj)) - return editpoints - - -def updateArchWall(obj, nodeIndex, v, alt_edit_mode): - if alt_edit_mode == 0: - # trim/extend endpoint - if nodeIndex == 0: - obj.Proxy.set_first_point(obj, v, local=True) - elif nodeIndex == 1: - obj.Proxy.set_last_point(obj, v, local=True) - elif alt_edit_mode == 1: - # rotate wall on the opposite endpoint (context menu "align") - import Draft - global_v = obj.getGlobalPlacement().multVec(v) - p1 = obj.Proxy.get_first_point(obj) - p2 = obj.Proxy.get_last_point(obj) - if nodeIndex == 0: - current_angle = DraftVecUtils.angle(App.Vector(1,0,0), p1.sub(p2)) - new_angle = DraftVecUtils.angle(App.Vector(1,0,0), global_v.sub(p2)) - Draft.rotate(obj, math.degrees(new_angle - current_angle), p2) - # obj.Proxy.set_first_point(obj, global_v) # this causes frequent hard crashes, probably to delay - elif nodeIndex == 1: - current_angle = DraftVecUtils.angle(App.Vector(1,0,0), p2.sub(p1)) - new_angle = DraftVecUtils.angle(App.Vector(1,0,0), global_v.sub(p1)) - Draft.rotate(obj, math.degrees(new_angle - current_angle), p1) - #obj.Proxy.set_last_point(obj, global_v) # this causes frequent hard crashes, probably to delay - - 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 get_structure_format(obj): - return (obj.ViewObject.DisplayMode, - obj.ViewObject.NodeSize, - obj.ViewObject.ShowNodes) - -def set_structure_editing_format(obj): - obj.ViewObject.DisplayMode = "Wireframe" - obj.ViewObject.NodeSize = 1 - obj.ViewObject.ShowNodes = True - -def restore_structure_format(obj, modes): - obj.ViewObject.DisplayMode = modes[0] - obj.ViewObject.NodeSize = modes[1] - obj.ViewObject.ShowNodes = modes[2] - -def getStructurePts(obj): - if obj.Nodes: editpoints = [] - for p in obj.Nodes: - editpoints.append(p) + # height of the wall + editpoints.append(App.Vector(0, 0, obj.Height)) return editpoints - else: - return None + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + ''' if node_idx == 0: + edit_arch.updateWall(obj, node_idx, v) + elif node_idx > 0: + if obj.Base: + if utils.get_type(obj.Base) in ["Wire", "Circle", "Rectangle", + "Polygon", "Sketch"]: + self.update(obj.Base, node_idx - 1, v)''' + if node_idx == 0: + vz = DraftVecUtils.project(v, App.Vector(0, 0, 1)) + if vz.Length > 0: + obj.Height = vz.Length -def updateStructure(obj, nodeIndex, v): - nodes = obj.Nodes - nodes[nodeIndex] = v - obj.Nodes = nodes + def get_edit_point_context_menu(self, obj, node_idx): + pass - -# SPACE-------------------------------------------------------------------- - -def getSpacePts(obj): - try: - editpoints = [] - editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject)) - return editpoints - except: + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): pass -def updateSpace(obj, nodeIndex, v): - if nodeIndex == 0: - obj.ViewObject.TextPosition = v +class ArchWindowGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(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 update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + pos = obj.Base.Placement.Base + if node_idx == 0: + obj.Base.Placement.Base = v + obj.Base.recompute() + if node_idx == 1: + obj.Width = pos.sub(v).Length + obj.Base.recompute() + if node_idx == 2: + obj.Height = pos.sub(v).Length + obj.Base.recompute() + for obj in obj.Hosts: + obj.recompute() + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass -# PANELS------------------------------------------------------------------- +class ArchStructureGuiTools(GuiTools): -def getPanelCutPts(obj): - editpoints = [] - if obj.TagPosition.Length == 0: - pos = obj.Shape.BoundBox.Center - else: - pos = obj.TagPosition - editpoints.append(pos) - return editpoints + def __init__(self): + pass + + def get_edit_points(self, obj): + if obj.Nodes: + editpoints = [] + for p in obj.Nodes: + editpoints.append(p) + return editpoints + else: + return None + + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + nodes = obj.Nodes + nodes[node_idx] = v + obj.Nodes = nodes + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + def get_object_style(self, obj): + return (obj.ViewObject.DisplayMode, + obj.ViewObject.NodeSize, + obj.ViewObject.ShowNodes) + + def set_object_editing_style(self, obj): + obj.ViewObject.DisplayMode = "Wireframe" + obj.ViewObject.NodeSize = 1 + obj.ViewObject.ShowNodes = True + + def restore_object_style(self, obj, modes): + obj.ViewObject.DisplayMode = modes[0] + obj.ViewObject.NodeSize = modes[1] + obj.ViewObject.ShowNodes = modes[2] -def updatePanelCut(obj, nodeIndex, v): - if nodeIndex == 0: - obj.TagPosition = v +class ArchSpaceGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(self, obj): + try: + editpoints = [] + editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject)) + return editpoints + except: + pass + + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + if node_idx == 0: + obj.ViewObject.TextPosition = v + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass -def getPanelSheetPts(obj): - editpoints = [] - editpoints.append(obj.TagPosition) - for o in obj.Group: - editpoints.append(o.Placement.Base) - return editpoints +class ArchPanelCutGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(self, obj): + editpoints = [] + if obj.TagPosition.Length == 0: + pos = obj.Shape.BoundBox.Center + else: + pos = obj.TagPosition + editpoints.append(pos) + return editpoints + + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + if node_idx == 0: + obj.TagPosition = v + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass -def updatePanelSheet(obj, nodeIndex, v): - if nodeIndex == 0: - obj.TagPosition = v - else: - obj.Group[nodeIndex-1].Placement.Base = v +class ArchPanelSheetGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(obj.TagPosition) + for o in obj.Group: + editpoints.append(o.Placement.Base) + return editpoints + + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + if node_idx == 0: + obj.TagPosition = v + else: + obj.Group[node_idx-1].Placement.Base = v + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + ## @} diff --git a/src/Mod/Draft/draftguitools/gui_edit_base_object.py b/src/Mod/Draft/draftguitools/gui_edit_base_object.py new file mode 100644 index 0000000000..7eb3511371 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_edit_base_object.py @@ -0,0 +1,90 @@ +# *************************************************************************** +# * 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 * +# * * +# *************************************************************************** +"""Provides support functions to edit Arch objects.""" +## @package gui_edit_arch_objects +# \ingroup draftguitools +# \brief Provides support functions to edit Arch objects. + +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Carlo Pavan") +__url__ = "https://www.freecadweb.org" + +## \addtogroup draftguitools +# @{ + + +class GuiTools: + """ Base class for object editing tools + """ + + def __init__(self): + pass + + def get_edit_points(self, obj): + """Return to Draft_Edit a list of vectors for the given object. + Remember to use object local coordinates system. + + Parameters: + obj: the object + """ + pass + + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + """Update the object from modified Draft_Edit point. + No need to recompute at the end. + + Parameters: + obj: the object + node_idx: number of the edited node + v: target vector of the node in object local coordinates system + alt_edit_mode: alternative edit mode to perform different operations + (usually can be selected from the Draft_Edit context menu) + default = 0 + """ + pass + + def get_edit_point_context_menu(self, obj, node_idx): + """ Return a list of Draft_Edit context menu actions. + """ + pass + + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + """ Do something when a Draft_Edit context menu action is triggered over a node. + """ + pass + + def get_object_style(self, obj): + pass + + def set_object_editing_style(self, obj): + pass + + def restore_object_style(self, obj, modes): + pass + + def init_preview_object(self, obj): + pass + + def update_preview_object(self, edit_command, obj, node_idx, v): + pass + +## @} diff --git a/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py b/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py index b0b961e819..dc8711093a 100644 --- a/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py +++ b/src/Mod/Draft/draftguitools/gui_edit_draft_objects.py @@ -51,465 +51,665 @@ import DraftVecUtils from draftutils.translate import translate import draftutils.utils as utils -def get_supported_draft_objects(): - return ["Wire", "BSpline", "Rectangle", "Circle", "Ellipse", "Polygon", - "BezCurve", - "Dimension", "LinearDimension"] +import draftguitools.gui_trackers as trackers + +from draftguitools.gui_edit_base_object import GuiTools -# ------------------------------------------------------------------------- -# EDIT OBJECT TOOLS : Line/Wire/BSpline -# ------------------------------------------------------------------------- -def getWirePts(obj): - editpoints = [] - for p in obj.Points: - editpoints.append(p) - return editpoints +class DraftWireGuiTools(GuiTools): -def updateWire(obj, nodeIndex, v): - pts = obj.Points - tol = 0.001 # TODO : Use default precision - if (nodeIndex == 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] + 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, please try again.") + App.Console.PrintMessage(_err + "\n") + return + + 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 - return - elif nodeIndex == 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, please try again.") - App.Console.PrintMessage(_err + "\n") - return - - 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[nodeIndex] = v - obj.Points = pts -# ------------------------------------------------------------------------- -# 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(App.Vector(0, 0, 0)) - editpoints.append(App.Vector(obj.Length, 0, 0)) - editpoints.append(App.Vector(0, obj.Height, 0)) - return editpoints - -def updateRectangle(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.multVec(v) - elif nodeIndex == 1: - obj.Length = DraftVecUtils.project(v, App.Vector(1,0,0)).Length - elif nodeIndex == 2: - obj.Height = DraftVecUtils.project(v, App.Vector(0,1,0)).Length - - -# ------------------------------------------------------------------------- -# 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(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(getArcStart(obj))#First endpoint - editpoints.append(getArcEnd(obj))#Second endpoint - editpoints.append(getArcMid(obj))#Midpoint - return editpoints - - -def updateCircle(obj, nodeIndex, v, alt_edit_mode=0): - if obj.FirstAngle == obj.LastAngle: - # object is a circle - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.multVec(v) - elif nodeIndex == 1: - obj.Radius = v.Length - - else: - # obj is an arc - if alt_edit_mode == 0: - import Part - if nodeIndex == 0: - # center point - p1 = getArcStart(obj) - p2 = getArcEnd(obj) - p0 = DraftVecUtils.project(v, 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(getArcStart(obj)) - p2 = obj.Placement.multVec(getArcMid(obj)) - p3 = obj.Placement.multVec(getArcEnd(obj)) - - if nodeIndex == 1: # first point - p1 = v - elif nodeIndex == 3: # midpoint - p2 = v - elif nodeIndex == 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 nodeIndex == 0: - obj.Placement.Base = obj.Placement.multVec(v) - else: - dangle = math.degrees(math.atan2(v[1],v[0])) - if nodeIndex == 1: - obj.FirstAngle = dangle - elif nodeIndex == 2: - obj.LastAngle = dangle - elif nodeIndex == 3: - obj.Radius = v.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 : Ellipse -# ------------------------------------------------------------------------- - -def getEllipsePts(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 updateEllipse(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.multVec(v) - elif nodeIndex == 1: - if v.Length >= obj.MinorRadius: - obj.MajorRadius = v.Length - else: - obj.MajorRadius = obj.MinorRadius - elif nodeIndex == 2: - if v.Length <= obj.MajorRadius: - obj.MinorRadius = v.Length - else: - obj.MinorRadius = obj.MajorRadius - obj.recompute() - - -# ------------------------------------------------------------------------- -# EDIT OBJECT TOOLS : Polygon -# ------------------------------------------------------------------------- - -def getPolygonPts(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 updatePolygon(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.multVec(v) - elif nodeIndex == 1: - obj.Radius = v.Length - 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 - - -# ------------------------------------------------------------------------- -# EDIT OBJECT TOOLS : BezCurve -# ------------------------------------------------------------------------- - -def updateBezCurve(obj, nodeIndex, v): #TODO: Fix it - pts = obj.Points - # DNC: check for coincident startpoint/endpoint to auto close the curve - tol = 0.001 - if ( ( nodeIndex == 0 ) and ( (v - pts[-1]).Length < tol) ) or ( - nodeIndex == 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, please try again.") - App.Console.PrintMessage(_err + "\n") - return + def get_edit_point_context_menu(self, obj, node_idx): + actions = ["delete point"] + return actions - pts = recomputePointsBezier(obj, pts, nodeIndex, v, obj.Degree, moveTrackers=False) + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + if action == "delete point": + self.delete_point(obj, node_idx) + edit_command.resetTrackers(obj) - 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[nodeIndex] = v - obj.Points = pts + 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 delete_point(self, obj, node_idx): + if len(obj.Points) <= 2: + _msg = translate("draft", "Active object must have more than two points/nodes") + App.Console.PrintWarning(_msg + "\n") + return + + pts = obj.Points + pts.pop(node_idx) + obj.Points = pts + + obj.recompute() -def recomputePointsBezier(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 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 +class DraftBSplineGuiTools(DraftWireGuiTools): - 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] + 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) + + +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 + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + +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, obj, node_idx): + actions = None + if obj.FirstAngle != obj.LastAngle: + if node_idx == 0: # user is over arc start point + actions = ["move arc"] + elif node_idx == 1: # user is over arc start point + actions = ["set first angle"] + elif node_idx == 2: # user is over arc end point + actions = ["set last angle"] + elif node_idx == 3: # user is over arc mid point + actions = ["set radius"] + if actions: + return actions + + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + if action in ("move arc", "set radius", + "set first angle", "set last angle"): + edit_command.alt_edit_mode = 1 + edit_command.startEditing(edit_command.event) + + 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_vectors(obj, obj.Shape.Vertexes[0].Point) + p2 = edit_command.localize_vectors(obj, obj.Shape.Vertexes[1].Point) + p0 = DraftVecUtils.project(edit_command.localize_vectors(obj, v), + edit_command.localize_vectors(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_vectors(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 == True: + 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 + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + +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() + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + +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 + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + 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 + # 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, please try again.") + 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, obj, node_idx): + if utils.get_type(obj) in ["Line", "Wire", "BSpline"]: + actions = ["delete point"] + elif utils.get_type(obj) in ["BezCurve"]: + actions = ["make sharp", "make tangent", + "make symmetric", "delete point"] + return actions + + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + if action == "delete point": + self.delete_point(obj, node_idx) + edit_command.resetTrackers(obj) + # Bezier curve menu + elif action in ["make sharp", "make tangent", "make symmetric"]: + if action == "make sharp": + self.smoothBezPoint(obj, node_idx, 'Sharp') + elif action == "make tangent": + self.smoothBezPoint(obj, node_idx, 'Tangent') + elif action == "make symmetric": + self.smoothBezPoint(obj, node_idx, 'Symmetric') + edit_command.resetTrackers(obj) + + + 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 delete_point(self, obj, node_idx): + if len(obj.Points) <= 2: + _msg = translate("draft", "Active object must have more than two points/nodes") + App.Console.PrintWarning(_msg + "\n") + return + + pts = obj.Points + pts.pop(node_idx) + obj.Points = pts + obj.Proxy.resetcontinuity(obj) + obj.recompute() + + 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 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][idx+1].set(pts[idx+1]) - if idx == 0 and obj.Closed: # move last pole - pts[-1] = pts [-1] + v -pts[idx] + # 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][-1].set(pts[-1]) + # self.trackers[obj.Name][changep].set(pts[changep]) + pts[idx] = v - 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 + return pts # returns the list of new points, taking into account knot continuity -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 + 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", - "Can't change Knot belonging to pole %d"%point) + "Selection is not a Knot") + "\n") return - if knot: + 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[changep] = obj.Proxy.modifytangentpole(pts[knot], - pts[keepp],pts[changep]) + pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1]) 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: + pts[1], pts[-1] = obj.Proxy.symmetricpoles(pts[0], pts[1], 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 + 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 ## @} diff --git a/src/Mod/Draft/draftguitools/gui_edit_part_objects.py b/src/Mod/Draft/draftguitools/gui_edit_part_objects.py index b8ed1887cd..456aea26bb 100644 --- a/src/Mod/Draft/draftguitools/gui_edit_part_objects.py +++ b/src/Mod/Draft/draftguitools/gui_edit_part_objects.py @@ -33,109 +33,153 @@ __url__ = "https://www.freecadweb.org" import FreeCAD as App import DraftVecUtils -def get_supported_part_objects(): - return ["Part", "Part::Line", "Part::Box", - "Part::Sphere", "Part::Cylinder", "Part::Cone" - ] - -# PART::LINE-------------------------------------------------------------- - -def getPartLinePts(obj): - editpoints = [] - editpoints.append(App.Vector(obj.X1,obj.Y1,obj.Z1)) - editpoints.append(App.Vector(obj.X2,obj.Y2,obj.Z2)) - return editpoints - -def updatePartLine(obj, nodeIndex, v): - if nodeIndex == 0: - obj.X1 = v.x - obj.Y1 = v.y - obj.Z1 = v.z - elif nodeIndex == 1: - obj.X2 = v.x - obj.Y2 = v.y - obj.Z2 = v.z +from draftguitools.gui_edit_base_object import GuiTools -# PART::BOX--------------------------------------------------------------- +class PartLineGuiTools(GuiTools): -def getPartBoxPts(obj): - editpoints = [] - editpoints.append(App.Vector(0, 0, 0)) - editpoints.append(App.Vector(obj.Length, 0, 0)) - editpoints.append(App.Vector(0, obj.Width, 0)) - editpoints.append(App.Vector(0, 0, obj.Height)) - return editpoints + def __init__(self): + pass -def updatePartBox(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.Base + v - elif nodeIndex == 1: - _vector = DraftVecUtils.project(v, App.Vector(1, 0, 0)) - obj.Length = _vector.Length - elif nodeIndex == 2: - _vector = DraftVecUtils.project(v, App.Vector(0, 1, 0)) - obj.Width = _vector.Length - elif nodeIndex == 3: - _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) - obj.Height = _vector.Length + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(App.Vector(obj.X1,obj.Y1,obj.Z1)) + editpoints.append(App.Vector(obj.X2,obj.Y2,obj.Z2)) + return editpoints -# Part::Cylinder -------------------------------------------------------------- + def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + if node_idx == 0: + obj.X1 = v.x + obj.Y1 = v.y + obj.Z1 = v.z + elif node_idx == 1: + obj.X2 = v.x + obj.Y2 = v.y + obj.Z2 = v.z -def getPartCylinderPts(obj): - editpoints = [] - editpoints.append(App.Vector(0, 0, 0)) - editpoints.append(App.Vector(obj.Radius, 0, 0)) - editpoints.append(App.Vector(0, 0, obj.Height)) - return editpoints - -def updatePartCylinder(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.Base + v - elif nodeIndex == 1: - if v.Length > 0.0: - obj.Radius = v.Length - elif nodeIndex == 2: - _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) - obj.Height = _vector.Length + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass -# Part::Cone -------------------------------------------------------------- +class PartBoxGuiTools(GuiTools): -def getPartConePts(obj): - editpoints = [] - editpoints.append(App.Vector(0, 0, 0)) - editpoints.append(App.Vector(obj.Radius1, 0, 0)) - editpoints.append(App.Vector(obj.Radius2, 0, obj.Height)) - editpoints.append(App.Vector(0, 0, obj.Height)) - return editpoints + def __init__(self): + pass -def updatePartCone(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.Base + v - elif nodeIndex == 1: - obj.Radius1 = v.Length # TODO: Perhaps better to project on the face? - elif nodeIndex == 2: - v.z = 0 - obj.Radius2 = v.Length # TODO: Perhaps better to project on the face? - elif nodeIndex == 3: # Height is last to have the priority on the radius - _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) - obj.Height = _vector.Length + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(App.Vector(0, 0, 0)) + editpoints.append(App.Vector(obj.Length, 0, 0)) + editpoints.append(App.Vector(0, obj.Width, 0)) + editpoints.append(App.Vector(0, 0, obj.Height)) + 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.Base + v + elif node_idx == 1: + _vector = DraftVecUtils.project(v, App.Vector(1, 0, 0)) + obj.Length = _vector.Length + elif node_idx == 2: + _vector = DraftVecUtils.project(v, App.Vector(0, 1, 0)) + obj.Width = _vector.Length + elif node_idx == 3: + _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) + obj.Height = _vector.Length + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass -# Part::Sphere -------------------------------------------------------------- +class PartCylinderGuiTools(GuiTools): -def getPartSpherePts(obj): - editpoints = [] - editpoints.append(App.Vector(0, 0, 0)) - editpoints.append(App.Vector(obj.Radius, 0, 0)) - return editpoints + def __init__(self): + pass -def updatePartSphere(obj, nodeIndex, v): - if nodeIndex == 0: - obj.Placement.Base = obj.Placement.Base + v - elif nodeIndex == 1: - if v.Length > 0.0: - obj.Radius = v.Length # TODO: Perhaps better to project on the face? + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(App.Vector(0, 0, 0)) + editpoints.append(App.Vector(obj.Radius, 0, 0)) + editpoints.append(App.Vector(0, 0, obj.Height)) + 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.Base + v + elif node_idx == 1: + if v.Length > 0.0: + obj.Radius = v.Length + elif node_idx == 2: + _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) + obj.Height = _vector.Length + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + +class PartConeGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(App.Vector(0, 0, 0)) + editpoints.append(App.Vector(obj.Radius1, 0, 0)) + editpoints.append(App.Vector(obj.Radius2, 0, obj.Height)) + editpoints.append(App.Vector(0, 0, obj.Height)) + 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.Base + v + elif node_idx == 1: + obj.Radius1 = v.Length # TODO: Perhaps better to project on the face? + elif node_idx == 2: + v.z = 0 + obj.Radius2 = v.Length # TODO: Perhaps better to project on the face? + elif node_idx == 3: # Height is last to have the priority on the radius + _vector = DraftVecUtils.project(v, App.Vector(0, 0, 1)) + obj.Height = _vector.Length + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass + + +class PartSphereGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(self, obj): + editpoints = [] + editpoints.append(App.Vector(0, 0, 0)) + editpoints.append(App.Vector(obj.Radius, 0, 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.Base + v + elif node_idx == 1: + if v.Length > 0.0: + obj.Radius = v.Length # TODO: Perhaps better to project on the face? + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass ## @} diff --git a/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py b/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py index aec11d4696..bd11f40159 100644 --- a/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py +++ b/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py @@ -33,47 +33,53 @@ __url__ = "https://www.freecadweb.org" ## \addtogroup draftguitools # @{ import FreeCAD as App - from draftutils.translate import translate - -def get_supported_sketcher_objects(): - return ["Sketch", "Sketcher::SketchObject", - ] - -# 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.getPoint(0,1)) - editpoints.append(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 +from draftguitools.gui_edit_base_object import GuiTools -def updateSketch(obj, nodeIndex, v): - """Move a single line sketch vertex a certain displacement. +class SketcherSketchObjectGuiTools(GuiTools): + + def __init__(self): + pass + + def get_edit_points(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.getPoint(0,1)) + editpoints.append(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 update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): + """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 node_idx == 0: + obj.movePoint(0, 1, v) + elif node_idx == 1: + obj.movePoint(0, 2, v) + obj.recompute() + + def get_edit_point_context_menu(self, obj, node_idx): + pass + + def evaluate_context_menu_action(self, edit_command, obj, node_idx, action): + pass - (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, v) - elif nodeIndex == 1: - obj.movePoint(0, 2, v) - obj.recompute() ## @} diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index 699ef51c91..41b7766506 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -415,15 +415,8 @@ def get_type(obj): If `obj` has a `Proxy`, it will return the value of `obj.Proxy.Type`. * If `obj` is a `Part.Shape`, returns `'Shape'` - * If `'Sketcher::SketchObject'`, returns `'Sketch'` - * If `'Part::Line'`, returns `'Part::Line'` - * If `'Part::Offset2D'`, returns `'Offset2D'` - * If `'Part::Feature'`, returns `'Part'` - * If `'App::Annotation'`, returns `'Annotation'` - * If `'Mesh::Feature'`, returns `'Mesh'` - * If `'Points::Feature'`, returns `'Points'` - * If `'App::DocumentObjectGroup'`, returns `'Group'` - * If `'App::Part'`, returns `'App::Part'` + + * If `obj` has a `TypeId`, returns `obj.TypeId` In other cases, it will return `'Unknown'`, or `None` if `obj` is `None`. @@ -433,27 +426,10 @@ def get_type(obj): return None if isinstance(obj, Part.Shape): return "Shape" - if "Proxy" in obj.PropertiesList: - if hasattr(obj.Proxy, "Type"): - return obj.Proxy.Type - if obj.isDerivedFrom("Sketcher::SketchObject"): - return "Sketch" - if (obj.TypeId == "Part::Line"): - return "Part::Line" - if (obj.TypeId == "Part::Offset2D"): - return "Offset2D" - if obj.isDerivedFrom("Part::Feature"): - return "Part" - if (obj.TypeId == "App::Annotation"): - return "Annotation" - if obj.isDerivedFrom("Mesh::Feature"): - return "Mesh" - if obj.isDerivedFrom("Points::Feature"): - return "Points" - if (obj.TypeId == "App::DocumentObjectGroup"): - return "Group" - if (obj.TypeId == "App::Part"): - return "App::Part" + if hasattr(obj, 'Proxy') and hasattr(obj.Proxy, "Type"): + return obj.Proxy.Type + if hasattr(obj, 'TypeId'): + return obj.TypeId return "Unknown"