From d3d18ac7e015aeb2f861b7a7c2b7dff760c76eaa Mon Sep 17 00:00:00 2001 From: paul <40677073+paullee0@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:54:27 +0800 Subject: [PATCH] [ArchWall] getExtrusionData() fix normal problem by adding get_shape_normal() (#12603) * [ArchWall] getExtrusionData() fix normal problem by adding get_shape_normal() In geometry.py - added get_shape_normal() - fixed is_straight_line() DraftGeomUtils.py - added get_shape_normal() FC Discussion - https://forum.freecad.org/viewtopic.php?p=726101#p726101 * [ArchWall] getExtrusionData() fix normal problem Follow-up following discussion at https://github.com/FreeCAD/FreeCAD/pull/12603 - delete trailing whitespaces - put Part.makePolygon() in try statement - study and findings at https://github.com/FreeCAD/FreeCAD/pull/12603#issuecomment-1969277743 In geometry.py - added get_shape_normal() - fixed is_straight_line() DraftGeomUtils.py - added get_shape_normal() FC Discussion - https://forum.freecad.org/viewtopic.php?p=726101#p726101 * Update geometry.py * Update geometry.py * Update geometry.py --------- Co-authored-by: Yorik van Havre --- src/Mod/Arch/ArchWall.py | 7 ++- src/Mod/Draft/DraftGeomUtils.py | 1 + src/Mod/Draft/draftgeoutils/geometry.py | 80 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index f7176d42ad..fb492b36d6 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -1210,7 +1210,10 @@ class _Wall(ArchComponent.Component): if not height: return None if obj.Normal == Vector(0,0,0): - normal = Vector(0,0,1) + import DraftGeomUtils + normal = DraftGeomUtils.get_shape_normal(obj.Base.Shape) + if normal == None: + normal = Vector(0,0,1) else: normal = Vector(obj.Normal) base = None @@ -1275,8 +1278,10 @@ class _Wall(ArchComponent.Component): # If the object is a single edge, use that as the # basewires. + # TODO 2023.11.26: Need to check if it isn't Sketch after all first # or use algorithm for Sketch altogether? + elif len(obj.Base.Shape.Edges) == 1: self.basewires = [Part.Wire(obj.Base.Shape.Edges)] diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 661efbaeec..0f76da33cb 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -70,6 +70,7 @@ from draftgeoutils.geometry import (findPerpendicular, get_spline_normal, getNormal, get_normal, + get_shape_normal, getRotation, isPlanar, is_planar, diff --git a/src/Mod/Draft/draftgeoutils/geometry.py b/src/Mod/Draft/draftgeoutils/geometry.py index bac846c3c5..9ad23f22d7 100644 --- a/src/Mod/Draft/draftgeoutils/geometry.py +++ b/src/Mod/Draft/draftgeoutils/geometry.py @@ -219,6 +219,65 @@ def get_spline_normal(edge, tol=-1): return None +def get_shape_normal(shape): + """Find the normal of a shape or list of points or colinear edges, if possible.""" + + # New function based on get_normal() drafted by @Roy_043 + # in discussion https://forum.freecad.org/viewtopic.php?p=717862#p717862 + # + # The normal would not be affected by the 3D view direction and flipped as + # get_normal would be. Placement of the Shape is taken into account in this + # new function instead : + # - Normal of a planar shape is usually bi-directional, this function return + # the one 'in the direction' of the shape's placement 'normal' (z-direction). + # As reference, shape.findPlane() favour positive axes, get_normal() + # favour the 'view direction' (obtained by getViewDirection() ). + # - Even when the Shape is an edge or colinear edges, its Placement is taken + # into account, and this function return one 'in the direction' of the + # shape's placement 'normal' (z-direction). + # https://forum.freecad.org/viewtopic.php?p=715850#p715850 + # The normal direction of a Draft wire (co-planar) or arc used to depends on + # the way the shape is constructed i.e. by vertexes in clockwise or + # anti-clockwise. This cryptic behaviour no longer matters. + + if shape.isNull(): + return None + + # Roy_043's remarks on the behavior of Shape.findPlane(): + # - https://forum.freecad.org/viewtopic.php?p=713993#p713993 + # Check if the shape is planar + # If True: check if the outer wire is closed: + # if True : Return a (plane with) normal based on the direction of the outer wire (CW or CCW). + # if False : ? Return a (plane with) normal that points towards positive global axes with a certain priority + # (probably? Z, Y and then X). + # If False: return None. + # + # Further remarks : + # - Tested Sketch with 2 colinear edges return a Plane with Axis + # - Tested Sketch with 1 edge return no Plane with no Axis + + shape_rot = shape.Placement.Rotation + if is_straight_line(shape): + start_edge = shape.Edges[0] + x_vec = start_edge.tangentAt(start_edge.FirstParameter) # Return vector is in global coordinate + local_x_vec = shape_rot.inverted().multVec(x_vec) # + local_rot = App.Rotation(local_x_vec, App.Vector(0, 1, 0), App.Vector(0, 0, 1), "XZY") + # see https://blog.freecad.org/2023/01/16/the-rotation-api-in-freecad/ for App.Rotation(vecx, vecy, vecz, string) + # discussion - https://forum.freecad.org/viewtopic.php?p=717738#p717738 + return shape_rot.multiply(local_rot).multVec(App.Vector(0, 0, 1)) + else: + plane = shape.findPlane() + + normal = plane.Axis + shape_normal = shape_rot.multVec(App.Vector(0, 0, 1)) + # Now, check the normal direction of the plane (plane.Axis). + # The final deduced normal should be 'in the direction' of the z-direction of the shape's placement, + # if plane.Axis is in the 'opposite' direction of the Z direction of the shape's placement, reverse it. + if normal.getAngle(shape_normal) > math.pi/2: + normal = normal.negative() + return normal + + def get_normal(shape, tol=-1): """Find the normal of a shape or list of points, if possible.""" @@ -329,6 +388,8 @@ def is_straight_line(shape, tol=-1): if len(shape.Edges) >= 1: start_edge = shape.Edges[0] dir_start_edge = start_edge.tangentAt(start_edge.FirstParameter) + point_start_edge = start_edge.firstVertex().Point + #set tolerance if tol <=0: err = shape.globalTolerance(tol) @@ -343,6 +404,25 @@ def is_straight_line(shape, tol=-1): # because sin(x) = x + O(x**3), for small angular deflection it's # enough use the cross product of directions (or dot with a normal) if (abs(edge.Length - first_point.distanceToPoint(last_point)) > err + + # https://forum.freecad.org/viewtopic.php?p=726101#p726101 + # Shape with parallel edges but not colinear used to return True + # by this function, below line added fixes this bug. + # Further remark on the below fix : + # - get_normal() use this function, may had created problems + # previously but not reported; on the other hand, this fix + # seems have no 'regression' created as get_normal use + # plane.Axis (with 3DView check). See get_shape_normal() + # which take into account shape's placement + # - other functions had been using this also : Keep In View to + # see if there is 'regression' : + # + # $ grep -rni "is_straight" ./ + # ./draftfunctions/upgrade.py + # ./draftgeoutils/geometry.py + # ./draftmake/make_wire.py + or first_point.distanceToLine(point_start_edge, dir_start_edge) > err \ + # or dir_start_edge.cross(dir_edge).Length > err): return False