From 1c918e9d9e73be230106e2f3c55e450b815f863f Mon Sep 17 00:00:00 2001 From: marioalexis Date: Fri, 21 May 2021 11:41:55 -0300 Subject: [PATCH 1/2] Draft: Improve alignToSelection method on WorkingPlane --- src/Mod/Draft/WorkingPlane.py | 120 +++++++++++++----- .../Draft/draftguitools/gui_selectplane.py | 31 +++-- 2 files changed, 106 insertions(+), 45 deletions(-) diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index 408ce7677b..9dffe9a25b 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -34,10 +34,16 @@ YZ, and XZ planes. # in FreeCAD and a couple of utility functions. import math +import lazy_loader.lazy_loader as lz import FreeCAD import DraftVecUtils from FreeCAD import Vector +from draftutils.translate import translate + +DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") +Part = lz.LazyLoader("Part", globals(), "Part") +FreeCADGui = lz.LazyLoader("FreeCADGui", globals(), "FreeCADGui") __title__ = "FreeCAD Working Plane utility" __author__ = "Ken Cline" @@ -603,12 +609,6 @@ class Plane: """Align the plane to a selection if it defines a plane. If the selection uniquely defines a plane it will be used. - Currently it only works with one object selected, a `'Face'`. - It extracts the shape of the object or subobject - and then calls `alignToFace(shape, offset)`. - - This method only works when `FreeCAD.GuiUp` is `True`, - that is, when the graphical interface is loaded. Parameter --------- @@ -621,43 +621,101 @@ class Plane: bool `True` if the operation was successful, and `False` otherwise. It returns `False` if the selection has no elements, - or if it has more than one element, or if the object is not derived from `'Part::Feature'` or if the object doesn't have a `Shape`. - To do - ----- - The method returns `False` if the selection list has more than - one element. - The method should search the list for objects like faces, points, - edges, wires, etc., and call the appropriate aligning submethod. - - The method could work for curves (`'Edge'` or `'Wire'`) but - `alignToCurve()` isn't fully implemented. - - When the interface is not loaded it should fail and print - a message, `FreeCAD.Console.PrintError()`. - See Also -------- alignToFace, alignToCurve """ - import FreeCADGui - sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name) - if len(sex) == 0: + sel_ex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name) + if not sel_ex: return False - elif len(sex) == 1: - if (not sex[0].Object.isDerivedFrom("Part::Feature") - or not sex[0].Object.Shape): + + shapes = list() + names = list() + for obj in sel_ex: + # check that the geometric property is a Part.Shape object + geom_is_shape = False + if isinstance(obj.Object, FreeCAD.GeoFeature): + geom = obj.Object.getPropertyOfGeometry() + if isinstance(geom, Part.Shape): + geom_is_shape = True + if not geom_is_shape: + FreeCAD.Console.PrintError(translate( + "draft", + "Object without Part.Shape geometry:'{}'\n".format( + obj.ObjectName))) return False - return (self.alignToFace(sex[0].Object.Shape, offset) - or (len(sex[0].SubObjects) == 1 - and self.alignToFace(sex[0].SubObjects[0], offset)) - or self.alignToCurve(sex[0].Object.Shape, offset)) + if geom.isNull(): + FreeCAD.Console.PrintError(translate( + "draft", + "Object with null Part.Shape geometry:'{}'\n".format( + obj.ObjectName))) + return False + if obj.HasSubObjects: + shapes.extend(obj.SubObjects) + names.extend([obj.ObjectName + "." + n for n in obj.SubElementNames]) + else: + shapes.append(geom) + names.append(obj.ObjectName) + + normal = None + for n in range(len(shapes)): + if not DraftGeomUtils.is_planar(shapes[n]): + FreeCAD.Console.PrintError(translate( + "draft","'{}' object is not planar\n".format(names[n]))) + return False + if not normal: + normal = DraftGeomUtils.get_normal(shapes[n]) + shape_ref = n + + # test if all shapes are coplanar + if normal: + for n in range(len(shapes)): + if not DraftGeomUtils.are_coplanar(shapes[shape_ref], shapes[n]): + FreeCAD.Console.PrintError(translate( + "draft","{} and {} aren't coplanar\n".format( + names[shape_ref],names[n]))) + return False else: - # len(sex) > 2, look for point and line, three points, etc. + # suppose all geometries are straight lines or points + points = [vertex.Point for shape in shapes for vertex in shape.Vertexes] + if len(points) >= 3: + poly = Part.makePolygon(points) + if not DraftGeomUtils.is_planar(poly): + FreeCAD.Console.PrintError(translate( + "draft","All Shapes must be coplanar\n")) + return False + normal = DraftGeomUtils.get_normal(poly) + else: + normal = None + + if not normal: + FreeCAD.Console.PrintError(translate( + "draft","Selected Shapes must define a plane\n")) return False + # set center of mass + ctr_mass = FreeCAD.Vector(0,0,0) + ctr_pts = FreeCAD.Vector(0,0,0) + mass = 0 + for shape in shapes: + if hasattr(shape, "CenterOfMass"): + ctr_mass += shape.CenterOfMass*shape.Mass + mass += shape.Mass + else: + ctr_pts += shape.Point + if mass > 0: + ctr_mass /= mass + # all shapes are vertexes + else: + ctr_mass = ctr_pts/len(shapes) + + self.alignToPointAndAxis(ctr_mass, normal, offset) + + return True + def setup(self, direction=None, point=None, upvec=None, force=False): """Set up the working plane if it exists but is undefined. diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index 3c309ae928..d29ecbaac9 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -75,6 +75,10 @@ class Draft_SelectPlane: def Activated(self): """Execute when the command is called.""" + # finish active Draft command if any + if FreeCAD.activeDraftCommand is not None: + FreeCAD.activeDraftCommand.finish() + # Reset variables self.view = Draft.get3DView() self.wpButton = FreeCADGui.draftToolBar.wplabel @@ -85,9 +89,6 @@ class Draft_SelectPlane: p = FreeCAD.DraftWorkingPlane self.states.append([p.u, p.v, p.axis, p.position]) - m = translate("draft", "Pick a face, 3 vertices or a WP Proxy to define the drawing plane") - _msg(m) - # Create task panel FreeCADGui.Control.closeDialog() self.taskd = task_selectplane.SelectPlaneTaskPanel() @@ -127,18 +128,20 @@ class Draft_SelectPlane: self.taskd.form.fieldSnapRadius.valueChanged.connect(self.onSetSnapRadius) # Try to find a WP from the current selection - if self.handle(): - return + if FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name): + if self.handle(): + pass + # Try another method + elif FreeCAD.DraftWorkingPlane.alignToSelection(): + FreeCADGui.Selection.clearSelection() + self.display(FreeCAD.DraftWorkingPlane.axis) + return None - # Try another method - if FreeCAD.DraftWorkingPlane.alignToSelection(): - FreeCADGui.Selection.clearSelection() - self.display(FreeCAD.DraftWorkingPlane.axis) - self.finish() - return - - # Execute the actual task panel - FreeCADGui.Control.showDialog(self.taskd) + # Execute the actual task panel delayed to catch posible active Draft command + todo.delay(FreeCADGui.Control.showDialog, self.taskd) + _msg(translate( + "draft", + "Pick a face, 3 vertices or a WP Proxy to define the drawing plane")) self.call = self.view.addEventCallback("SoEvent", self.action) def finish(self, close=False): From a6ef3e656b288b968ce832670a15d84b39375681 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sun, 6 Jun 2021 21:20:08 -0300 Subject: [PATCH 2/2] Draft: Check null shape in geometry.py functions --- src/Mod/Draft/draftgeoutils/geometry.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftgeoutils/geometry.py b/src/Mod/Draft/draftgeoutils/geometry.py index 00b5eccd44..099fde42c9 100644 --- a/src/Mod/Draft/draftgeoutils/geometry.py +++ b/src/Mod/Draft/draftgeoutils/geometry.py @@ -199,6 +199,9 @@ def findDistance(point, edge, strict=False): def get_spline_normal(edge, tol=-1): """Find the normal of a BSpline edge.""" + if edge.isNull(): + return None + if is_straight_line(shape, tol): return None @@ -230,9 +233,11 @@ def get_normal(shape, tol=-1): return None # for shapes - if is_straight_line(shape, tol): + if shape.isNull(): return None + if is_straight_line(shape, tol): + return None else: plane = find_plane(shape, tol) if plane: @@ -273,6 +278,7 @@ def is_planar(shape, tol=-1): poly = Part.makePolygon(shape) if is_straight_line(poly, tol): return True + plane = poly.findPlane(tol) if plane: return True @@ -280,6 +286,9 @@ def is_planar(shape, tol=-1): return False # for shapes + if shape.isNull(): + return False + # because Part.Shape.findPlane return None for Vertex and straight edges if shape.ShapeType == "Vertex": return True @@ -300,6 +309,9 @@ def is_straight_line(shape, tol=-1): plane and normal to straight wires creating privileged directions and to deal with straight wires with overlapped edges.""" + if shape.isNull(): + return False + if len(shape.Faces) != 0: return False @@ -332,6 +344,9 @@ def is_straight_line(shape, tol=-1): def are_coplanar(shape_a, shape_b, tol=-1): """Return True if exist a plane containing both shapes.""" + if shape_a.isNull() or shape_b.isNull(): + return False + if not is_planar(shape_a, tol) or not is_planar(shape_b, tol): return False @@ -391,6 +406,9 @@ def get_spline_surface_normal(shape, tol=-1): """Check if shape formed by BSpline surfaces is planar and get normal. If shape is not planar return None.""" + if shape.isNull(): + return None + if len(shape.Faces) == 0: return None @@ -431,6 +449,9 @@ def find_plane(shape, tol=-1): Use this function as a workaround due Part.Shape.findPlane fail to find plane on BSpline surfaces.""" + if shape.isNull(): + return None + if shape.ShapeType == "Vertex": return None