From 7a764fb672b5051c10be72d5ac4878871e953a35 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 22 May 2020 22:46:24 -0500 Subject: [PATCH] Draft: move functions to draftgeoutils.offsets --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftGeomUtils.py | 341 +---------------- src/Mod/Draft/draftgeoutils/offsets.py | 486 +++++++++++++++++++++++++ 3 files changed, 490 insertions(+), 338 deletions(-) create mode 100644 src/Mod/Draft/draftgeoutils/offsets.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 81bbafd91318..8150d1e1b363 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -39,6 +39,7 @@ SET (Draft_geoutils draftgeoutils/wires.py draftgeoutils/arcs.py draftgeoutils/fillets.py + draftgeoutils/offsets.py ) SET(Draft_tests diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index c42d6bfb259d..4ab9acfdfe7c 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -107,72 +107,7 @@ from draftgeoutils.intersections import wiresIntersect - -def pocket2d(shape, offset): - """pocket2d(shape,offset): return a list of wires obtained from offsetting the wires from the given shape - by the given offset, and intersection if needed.""" - # find the outer wire - l = 0 - outerWire = None - innerWires = [] - for w in shape.Wires: - if w.BoundBox.DiagonalLength > l: - outerWire = w - l = w.BoundBox.DiagonalLength - if not outerWire: - return [] - for w in shape.Wires: - if w.hashCode() != outerWire.hashCode(): - innerWires.append(w) - o = outerWire.makeOffset(-offset) - if not o.Wires: - return [] - offsetWires = o.Wires - #print("base offset wires:",offsetWires) - if not innerWires: - return offsetWires - for innerWire in innerWires: - i = innerWire.makeOffset(offset) - if len(innerWire.Edges) == 1: - e = innerWire.Edges[0] - if isinstance(e.Curve,Part.Circle): - e = Part.makeCircle(e.Curve.Radius+offset,e.Curve.Center,e.Curve.Axis) - i = Part.Wire(e) - if i.Wires: - #print("offsetting island ",innerWire," : ",i.Wires) - for w in i.Wires: - added = False - #print("checking wire ",w) - k = list(range(len(offsetWires))) - for j in k: - #print("checking against existing wire ",j) - ow = offsetWires[j] - if ow: - if wiresIntersect(w,ow): - #print("intersect") - f1 = Part.Face(ow) - f2 = Part.Face(w) - f3 = f1.cut(f2) - #print("made new wires: ",f3.Wires) - offsetWires[j] = f3.Wires[0] - if len(f3.Wires) > 1: - #print("adding more") - offsetWires.extend(f3.Wires[1:]) - added = True - else: - a = w.BoundBox - b = ow.BoundBox - if (a.XMin <= b.XMin) and (a.YMin <= b.YMin) and (a.ZMin <= b.ZMin) and (a.XMax >= b.XMax) and (a.YMax >= b.YMax) and (a.ZMax >= b.ZMax): - #print("this wire is bigger than the outer wire") - offsetWires[j] = None - added = True - #else: - #print("doesn't intersect") - if not added: - #print("doesn't intersect with any other") - offsetWires.append(w) - offsetWires = [o for o in offsetWires if o != None] - return offsetWires +from draftgeoutils.offsets import pocket2d from draftgeoutils.edges import orientEdge @@ -253,29 +188,7 @@ def findClosest(basepoint, pointslist): from draftgeoutils.geometry import findPerpendicular -def offset(edge, vector, trim=False): - """ - offset(edge,vector) - returns a copy of the edge at a certain (vector) distance - if the edge is an arc, the vector will be added at its first point - and a complete circle will be returned - """ - if (not isinstance(edge,Part.Shape)) or (not isinstance(vector,FreeCAD.Vector)): - return None - if geomType(edge) == "Line": - v1 = Vector.add(edge.Vertexes[0].Point, vector) - v2 = Vector.add(edge.Vertexes[-1].Point, vector) - return Part.LineSegment(v1,v2).toShape() - elif geomType(edge) == "Circle": - rad = edge.Vertexes[0].Point.sub(edge.Curve.Center) - curve = Part.Circle(edge.Curve) - curve.Radius = Vector.add(rad,vector).Length - if trim: - return Part.ArcOfCircle(curve,edge.FirstParameter,edge.LastParameter).toShape() - else: - return curve.toShape() - else: - return None +from draftgeoutils.offsets import offset from draftgeoutils.wires import isReallyClosed @@ -293,255 +206,7 @@ def offset(edge, vector, trim=False): from draftgeoutils.geometry import calculatePlacement -def offsetWire(wire, dvec, bind=False, occ=False, - widthList=None, offsetMode=None, alignList=[], - normal=None, basewireOffset=0): # offsetMode="BasewireMode" or None - """ - offsetWire(wire,vector,[bind]): offsets the given wire along the given - vector. The vector will be applied at the first vertex of the wire. If bind - is True (and the shape is open), the original wire and the offsetted one - are bound by 2 edges, forming a face. - - If widthList is provided (values only, not lengths - i.e. no unit), - each value will be used to offset each corresponding edge in the wire. - - (The 1st value overrides 'dvec' for 1st segment of wire; - if a value is zero, value of 'widthList[0]' will follow; - if widthList[0]' == 0, but dvec still provided, dvec will be followed) - - If alignList is provided, - each value will be used to offset each corresponding edge in the wire with corresponding index. - - OffsetWire() is now aware of width and align per edge (Primarily for use with ArchWall based on Sketch object ) - - 'dvec' vector to offset is now derived (and can be ignored) in this function if widthList and alignList are provided - 'dvec' to be obsolete in future ? - - 'basewireOffset' corresponds to 'offset' in ArchWall which offset the basewire before creating the wall outline - """ - - # Accept 'wire' as a list of edges (use the list directly), or previously as a wire or a face (Draft Wire with MakeFace True or False supported) - - if isinstance(wire,Part.Wire) or isinstance(wire,Part.Face): - edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) - elif isinstance(wire, list): - if isinstance(wire[0],Part.Edge): - edges = wire.copy() - wire = Part.Wire( Part.__sortEdges__(edges) ) # How to avoid __sortEdges__ again? Make getNormal directly tackle edges ? - else: - print ("Either Part.Wire or Part.Edges should be provided, returning None ") - return None - - # For sketch with a number of wires, getNormal() may result in different direction for each wire - # The 'normal' parameter, if provided e.g. by ArchWall, allows normal over different wires e.g. in a Sketch be consistent (over different calls of this function) - if normal: - norm = normal - else: - norm = getNormal(wire) # norm = Vector(0, 0, 1) - - closed = isReallyClosed(wire) - nedges = [] - if occ: - l=abs(dvec.Length) - if not l: return None - if wire.Wires: - wire = wire.Wires[0] - else: - wire = Part.Wire(edges) - try: - off = wire.makeOffset(l) - except: - return None - else: - return off - - # vec of first edge depends on its geometry - e = edges[0] - - # Make a copy of alignList - to avoid changes in this function become starting input of next call of this function ? - # https://www.dataquest.io/blog/tutorial-functions-modify-lists-dictionaries-python/ - # alignListC = alignList.copy() # Only Python 3 - alignListC = list(alignList) # Python 2 and 3 - - # Check the direction / offset of starting edge - firstDir = None - try: - if alignListC[0] == 'Left': - firstDir = 1 - firstAlign = 'Left' - elif alignListC[0] == 'Right': - firstDir = -1 - firstAlign = 'Right' - elif alignListC[0] == 'Center': - firstDir = 1 - firstAlign = 'Center' - except: - pass # Should no longer happen for ArchWall - as aligns are 'filled in' by ArchWall - - # If not provided by alignListC checked above, check the direction of offset in dvec (not 'align') - if not firstDir: ## TODO Should check if dvec is provided or not ('legacy/backward-compatible' mode) - if isinstance(e.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle - v0 = e.Vertexes[0].Point.sub(e.Curve.Center) - else: - v0 = vec(e).cross(norm) - # check against dvec provided for the offset direction - would not know if dvec is vector of width (Left/Right Align) or width/2 (Center Align) - dvec0 = DraftVecUtils.scaleTo(v0,dvec.Length) - if DraftVecUtils.equals(dvec0,dvec): # if dvec0 == dvec: - firstDir = 1 # "Left Offset" (Left Align or 'left offset' in Centre Align) - firstAlign = 'Left' - alignListC.append('Left') - elif DraftVecUtils.equals(dvec0,dvec.negative()): # elif dvec0 == dvec.negative(): - firstDir = -1 # "Right Offset" (Right Align or 'right offset' in Centre Align) - firstAlign = 'Right' - alignListC.append('Right') - else: - print (" something wrong with firstDir ") - firstAlign = 'Left' - alignListC.append('Left') - - for i in range(len(edges)): - # make a copy so it do not reverse the self.baseWires edges pointed to by _Wall.getExtrusionData() ? - curredge = edges[i].copy() - - # record first edge's Orientation, Dir, Align and set Delta - if i == 0: - firstOrientation = curredge.Vertexes[0].Orientation # TODO Could be edge.Orientation in fact # "Forward" or "Reversed" - curOrientation = firstOrientation - curDir = firstDir - curAlign = firstAlign - delta = dvec - - # record current edge's Orientation, and set Delta - if i != 0: #else: - if isinstance(curredge.Curve,Part.Circle): # TODO Should also calculate 1st edge direction above - delta = curredge.Vertexes[0].Point.sub(curredge.Curve.Center) - else: - delta = vec(curredge).cross(norm) - curOrientation = curredge.Vertexes[0].Orientation # TODO Could be edge.Orientation in fact - - # Consider individual edge width - if widthList: # ArchWall should now always provide widthList - try: - if widthList[i] > 0: - delta = DraftVecUtils.scaleTo(delta, widthList[i]) - elif dvec: - delta = DraftVecUtils.scaleTo(delta, dvec.Length) - else: - #just hardcoded default value as ArchWall would provide if dvec is not provided either - delta = DraftVecUtils.scaleTo(delta, 200) - except: - if dvec: - delta = DraftVecUtils.scaleTo(delta, dvec.Length) - else: - #just hardcoded default value as ArchWall would provide if dvec is not provided either - delta = DraftVecUtils.scaleTo(delta, 200) - else: - delta = DraftVecUtils.scaleTo(delta,dvec.Length) - - # Consider individual edge Align direction - ArchWall should now always provide alignList - if i == 0: - if alignListC[0] == 'Center': - delta = DraftVecUtils.scaleTo(delta, delta.Length/2) - # No need to do anything for 'Left' and 'Right' as original dvec have set both the direction and amount of offset correct - # elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': - if i != 0: - try: - if alignListC[i] == 'Left': - curDir = 1 - curAlign = 'Left' - elif alignListC[i] == 'Right': - curDir = -1 - curAlign = 'Right' - delta = delta.negative() - elif alignListC[i] == 'Center': - curDir = 1 - curAlign = 'Center' - delta = DraftVecUtils.scaleTo(delta, delta.Length/2) - except: - curDir = firstDir - curAlign = firstAlign - if firstAlign == 'Right': - delta = delta.negative() - elif firstAlign == 'Center': - delta = DraftVecUtils.scaleTo(delta, delta.Length/2) - - # Consider whether generating the 'offset wire' or the 'base wire' - if offsetMode is None: - # Consider if curOrientation and/or curDir match their firstOrientation/firstDir - to determine whether and how to offset the current edge - if (curOrientation == firstOrientation) != (curDir == firstDir): # i.e. xor - if curAlign in ['Left', 'Right']: - nedge = curredge - elif curAlign == 'Center': - delta = delta.negative() - nedge = offset(curredge,delta,trim=True) - else: - # if curAlign in ['Left', 'Right']: # elif curAlign == 'Center': # Both conditions same result.. - if basewireOffset: # ArchWall has an Offset properties for user to offset the basewire before creating the base profile of wall (not applicable to 'Center' align) - delta = DraftVecUtils.scaleTo(delta, delta.Length+basewireOffset) - nedge = offset(curredge,delta,trim=True) - - if curOrientation == "Reversed": # TODO arc always in counter-clockwise directinon ... ( not necessarily 'reversed') - if not isinstance(curredge.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle - # if not arc/circle, assume straight line, reverse it - nedge = Part.Edge(nedge.Vertexes[1],nedge.Vertexes[0]) - else: - # if arc/circle - #Part.ArcOfCircle(edge.Curve, edge.FirstParameter,edge.LastParameter,edge.Curve.Axis.z>0) - midParameter = nedge.FirstParameter + (nedge.LastParameter - nedge.FirstParameter)/2 - midOfArc = nedge.valueAt(midParameter) - nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, midOfArc, nedge.Vertexes[0].Point).toShape() - # TODO any better solution than to calculate midpoint of arc to reverse ? - - elif offsetMode in ["BasewireMode"]: - if not ( (curOrientation == firstOrientation) != (curDir == firstDir) ): - if curAlign in ['Left', 'Right']: - if basewireOffset: # ArchWall has an Offset properties for user to offset the basewire before creating the base profile of wall (not applicable to 'Center' align) - delta = DraftVecUtils.scaleTo(delta, basewireOffset) - nedge = offset(curredge,delta,trim=True) - else: - nedge = curredge - elif curAlign == 'Center': - delta = delta.negative() - nedge = offset(curredge,delta,trim=True) - else: - if curAlign in ['Left', 'Right']: - if basewireOffset: # ArchWall has an Offset properties for user to offset the basewire before creating the base profile of wall (not applicable to 'Center' align) - delta = DraftVecUtils.scaleTo(delta, delta.Length+basewireOffset) - nedge = offset(curredge,delta,trim=True) - - elif curAlign == 'Center': - nedge = offset(curredge,delta,trim=True) - if curOrientation == "Reversed": - if not isinstance(curredge.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle - # if not arc/circle, assume straight line, reverse it - nedge = Part.Edge(nedge.Vertexes[1],nedge.Vertexes[0]) - else: - # if arc/circle - #Part.ArcOfCircle(edge.Curve, edge.FirstParameter,edge.LastParameter,edge.Curve.Axis.z>0) - midParameter = nedge.FirstParameter + (nedge.LastParameter - nedge.FirstParameter)/2 - midOfArc = nedge.valueAt(midParameter) - nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, midOfArc, nedge.Vertexes[0].Point).toShape() - # TODO any better solution than to calculate midpoint of arc to reverse ? - else: - print(" something wrong ") - return - if not nedge: - return None - nedges.append(nedge) - - if len(edges) >1: - nedges = connect(nedges,closed) - else: - nedges = Part.Wire(nedges[0]) - - if bind and not closed: - e1 = Part.LineSegment(edges[0].Vertexes[0].Point,nedges[0].Vertexes[0].Point).toShape() - e2 = Part.LineSegment(edges[-1].Vertexes[-1].Point,nedges[-1].Vertexes[-1].Point).toShape() - alledges = edges.extend(nedges) - alledges = alledges.extend([e1,e2]) - w = Part.Wire(alledges) - return w - else: - return nedges +from draftgeoutils.offsets import offsetWire from draftgeoutils.intersections import connect diff --git a/src/Mod/Draft/draftgeoutils/offsets.py b/src/Mod/Draft/draftgeoutils/offsets.py new file mode 100644 index 000000000000..2c161f681f74 --- /dev/null +++ b/src/Mod/Draft/draftgeoutils/offsets.py @@ -0,0 +1,486 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * 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. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides various functions for offset operations.""" +## @package offsets +# \ingroup DRAFTGEOUTILS +# \brief Provides various functions for offset operations. + +import lazy_loader.lazy_loader as lz + +import FreeCAD +import DraftVecUtils + +from draftgeoutils.general import geomType, vec +from draftgeoutils.intersections import wiresIntersect, connect +from draftgeoutils.geometry import getNormal +from draftgeoutils.wires import isReallyClosed + +# Delay import of module until first use because it is heavy +Part = lz.LazyLoader("Part", globals(), "Part") + + +def pocket2d(shape, offset): + """Return a list of wires obtained from offseting wires from the shape. + + Return a list of wires obtained from offsetting the wires + from the given shape by the given offset, and intersection if needed. + """ + # find the outer wire + length = 0 + outerWire = None + innerWires = [] + for w in shape.Wires: + if w.BoundBox.DiagonalLength > length: + outerWire = w + length = w.BoundBox.DiagonalLength + + if not outerWire: + return [] + + for w in shape.Wires: + if w.hashCode() != outerWire.hashCode(): + innerWires.append(w) + + o = outerWire.makeOffset(-offset) + + if not o.Wires: + return [] + + offsetWires = o.Wires + # print("base offset wires:", offsetWires) + if not innerWires: + return offsetWires + + for innerWire in innerWires: + i = innerWire.makeOffset(offset) + if len(innerWire.Edges) == 1: + e = innerWire.Edges[0] + if isinstance(e.Curve, Part.Circle): + e = Part.makeCircle(e.Curve.Radius + offset, + e.Curve.Center, + e.Curve.Axis) + i = Part.Wire(e) + if i.Wires: + # print("offsetting island ", innerWire, " : ", i.Wires) + for w in i.Wires: + added = False + # print("checking wire ",w) + k = list(range(len(offsetWires))) + for j in k: + # print("checking against existing wire ", j) + ow = offsetWires[j] + if ow: + if wiresIntersect(w, ow): + # print("intersect") + f1 = Part.Face(ow) + f2 = Part.Face(w) + f3 = f1.cut(f2) + # print("made new wires: ", f3.Wires) + offsetWires[j] = f3.Wires[0] + if len(f3.Wires) > 1: + # print("adding more") + offsetWires.extend(f3.Wires[1:]) + added = True + else: + a = w.BoundBox + b = ow.BoundBox + if ((a.XMin <= b.XMin) + and (a.YMin <= b.YMin) + and (a.ZMin <= b.ZMin) + and (a.XMax >= b.XMax) + and (a.YMax >= b.YMax) + and (a.ZMax >= b.ZMax)): + # print("this wire is bigger than " + # "the outer wire") + offsetWires[j] = None + added = True + # else: + # print("doesn't intersect") + if not added: + # print("doesn't intersect with any other") + offsetWires.append(w) + offsetWires = [o for o in offsetWires if o is not None] + + return offsetWires + + +def offset(edge, vector, trim=False): + """Return a copy of the edge at a certain vector offset. + + If the edge is an arc, the vector will be added at its first point + and a complete circle will be returned. + + None if there is a problem. + """ + if (not isinstance(edge, Part.Shape) + or not isinstance(vector, FreeCAD.Vector)): + return None + + if geomType(edge) == "Line": + v1 = FreeCAD.Vector.add(edge.Vertexes[0].Point, vector) + v2 = FreeCAD.Vector.add(edge.Vertexes[-1].Point, vector) + return Part.LineSegment(v1, v2).toShape() + + elif geomType(edge) == "Circle": + rad = edge.Vertexes[0].Point.sub(edge.Curve.Center) + curve = Part.Circle(edge.Curve) + curve.Radius = FreeCAD.Vector.add(rad, vector).Length + if trim: + return Part.ArcOfCircle(curve, + edge.FirstParameter, + edge.LastParameter).toShape() + else: + return curve.toShape() + else: + return None + + +def offsetWire(wire, dvec, bind=False, occ=False, + widthList=None, offsetMode=None, alignList=[], + normal=None, basewireOffset=0): + """Offset the wire along the given vector. + + Parameters + ---------- + wire as a list of edges (use the list directly), + or previously as a wire or a face (Draft Wire with MakeFace True + or False supported). + + The vector will be applied at the first vertex of the wire. If bind + is True (and the shape is open), the original wire and the offsetted one + are bound by 2 edges, forming a face. + + If widthList is provided (values only, not lengths - i.e. no unit), + each value will be used to offset each corresponding edge in the wire. + + The 1st value overrides 'dvec' for 1st segment of wire; + if a value is zero, value of 'widthList[0]' will follow; + if widthList[0]' == 0, but dvec still provided, dvec will be followed + + offsetMode="BasewireMode" or None + + If alignList is provided, + each value will be used to offset each corresponding edge + in the wire with corresponding index. + + 'basewireOffset' corresponds to 'offset' in ArchWall which offset + the basewire before creating the wall outline + + OffsetWire() is now aware of width and align per edge + Primarily for use with ArchWall based on Sketch object + + To Do + ----- + `dvec` vector to offset is now derived (and can be ignored) + in this function if widthList and alignList are provided + - 'dvec' to be obsolete in future? + """ + if isinstance(wire, Part.Wire) or isinstance(wire, Part.Face): + # Seems has repeatedly sortEdges, remark out here + # edges = Part.__sortEdges__(wire.Edges) + edges = wire.Edges + elif isinstance(wire, list): + if isinstance(wire[0], Part.Edge): + edges = wire.copy() + # How to avoid __sortEdges__ again? + # Make getNormal directly tackle edges? + wire = Part.Wire(Part.__sortEdges__(edges)) + else: + print("Either Part.Wire or Part.Edges should be provided, " + "returning None") + return None + + # For sketch with a number of wires, getNormal() may result + # in different direction for each wire. + # The 'normal' parameter, if provided e.g. by ArchWall, + # allows normal over different wires e.g. in a Sketch be consistent + # (over different calls of this function) + if normal: + norm = normal + else: + norm = getNormal(wire) # norm = Vector(0, 0, 1) + + closed = isReallyClosed(wire) + nedges = [] + if occ: + length = abs(dvec.Length) + if not length: + return None + + if wire.Wires: + wire = wire.Wires[0] + else: + wire = Part.Wire(edges) + + try: + off = wire.makeOffset(length) + except Part.OCCError: + return None + else: + return off + + # vec of first edge depends on its geometry + e = edges[0] + + # Make a copy of alignList - to avoid changes in this function + # become starting input of next call of this function? + # https://www.dataquest.io/blog/tutorial-functions-modify-lists-dictionaries-python/ + # alignListC = alignList.copy() # Only Python 3 + alignListC = list(alignList) # Python 2 and 3 + + # Check the direction / offset of starting edge + firstDir = None + try: + if alignListC[0] == 'Left': + firstDir = 1 + firstAlign = 'Left' + elif alignListC[0] == 'Right': + firstDir = -1 + firstAlign = 'Right' + elif alignListC[0] == 'Center': + firstDir = 1 + firstAlign = 'Center' + except IndexError: + # Should no longer happen for ArchWall + # as aligns are 'filled in' by ArchWall + pass + + # If not provided by alignListC checked above, check the direction + # of offset in dvec (not 'align'). + + # TODO Should check if dvec is provided or not + # ('legacy/backward-compatible' mode) + if not firstDir: + # need to test against Part.Circle, not Part.ArcOfCircle + if isinstance(e.Curve, Part.Circle): + v0 = e.Vertexes[0].Point.sub(e.Curve.Center) + else: + v0 = vec(e).cross(norm) + # check against dvec provided for the offset direction + # would not know if dvec is vector of width (Left/Right Align) + # or width/2 (Center Align) + dvec0 = DraftVecUtils.scaleTo(v0, dvec.Length) + if DraftVecUtils.equals(dvec0, dvec): + # "Left Offset" (Left Align or 'left offset' in Centre Align) + firstDir = 1 + firstAlign = 'Left' + alignListC.append('Left') + elif DraftVecUtils.equals(dvec0, dvec.negative()): + # "Right Offset" (Right Align or 'right offset' in Centre Align) + firstDir = -1 + firstAlign = 'Right' + alignListC.append('Right') + else: + print(" something wrong with firstDir ") + firstAlign = 'Left' + alignListC.append('Left') + + for i in range(len(edges)): + # make a copy so it do not reverse the self.baseWires edges + # pointed to by _Wall.getExtrusionData()? + curredge = edges[i].copy() + + # record first edge's Orientation, Dir, Align and set Delta + if i == 0: + # TODO Could be edge.Orientation in fact + # "Forward" or "Reversed" + firstOrientation = curredge.Vertexes[0].Orientation + curOrientation = firstOrientation + curDir = firstDir + curAlign = firstAlign + delta = dvec + + # record current edge's Orientation, and set Delta + if i != 0: # else: + # TODO Should also calculate 1st edge direction above + if isinstance(curredge.Curve, Part.Circle): + delta = curredge.Vertexes[0].Point.sub(curredge.Curve.Center) + else: + delta = vec(curredge).cross(norm) + # TODO Could be edge.Orientation in fact + curOrientation = curredge.Vertexes[0].Orientation + + # Consider individual edge width + if widthList: # ArchWall should now always provide widthList + try: + if widthList[i] > 0: + delta = DraftVecUtils.scaleTo(delta, widthList[i]) + elif dvec: + delta = DraftVecUtils.scaleTo(delta, dvec.Length) + else: + # just hardcoded default value as ArchWall would provide + # if dvec is not provided either + delta = DraftVecUtils.scaleTo(delta, 200) + except Part.OCCError: + if dvec: + delta = DraftVecUtils.scaleTo(delta, dvec.Length) + else: + # just hardcoded default value as ArchWall would provide + # if dvec is not provided either + delta = DraftVecUtils.scaleTo(delta, 200) + else: + delta = DraftVecUtils.scaleTo(delta, dvec.Length) + + # Consider individual edge Align direction + # - ArchWall should now always provide alignList + if i == 0: + if alignListC[0] == 'Center': + delta = DraftVecUtils.scaleTo(delta, delta.Length/2) + # No need to do anything for 'Left' and 'Right' as original dvec + # have set both the direction and amount of offset correct + # elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': + if i != 0: + try: + if alignListC[i] == 'Left': + curDir = 1 + curAlign = 'Left' + elif alignListC[i] == 'Right': + curDir = -1 + curAlign = 'Right' + delta = delta.negative() + elif alignListC[i] == 'Center': + curDir = 1 + curAlign = 'Center' + delta = DraftVecUtils.scaleTo(delta, delta.Length/2) + except IndexError: + curDir = firstDir + curAlign = firstAlign + if firstAlign == 'Right': + delta = delta.negative() + elif firstAlign == 'Center': + delta = DraftVecUtils.scaleTo(delta, delta.Length/2) + + # Consider whether generating the 'offset wire' or the 'base wire' + if offsetMode is None: + # Consider if curOrientation and/or curDir match their + # firstOrientation/firstDir - to determine whether + # and how to offset the current edge + + # This is a xor + if (curOrientation == firstOrientation) != (curDir == firstDir): + if curAlign in ['Left', 'Right']: + nedge = curredge + elif curAlign == 'Center': + delta = delta.negative() + nedge = offset(curredge, delta, trim=True) + else: + # if curAlign in ['Left', 'Right']: + # elif curAlign == 'Center': # Both conditions same result. + # ArchWall has an Offset properties for user to offset + # the basewire before creating the base profile of wall + # (not applicable to 'Center' align) + if basewireOffset: + delta = DraftVecUtils.scaleTo(delta, + delta.Length + basewireOffset) + nedge = offset(curredge, delta, trim=True) + + # TODO arc always in counter-clockwise directinon + # ... ( not necessarily 'reversed') + if curOrientation == "Reversed": + # need to test against Part.Circle, not Part.ArcOfCircle + if not isinstance(curredge.Curve, Part.Circle): + # if not arc/circle, assume straight line, reverse it + nedge = Part.Edge(nedge.Vertexes[1], nedge.Vertexes[0]) + else: + # if arc/circle + # Part.ArcOfCircle(edge.Curve, + # edge.FirstParameter, edge.LastParameter, + # edge.Curve.Axis.z > 0) + midParameter = nedge.FirstParameter + (nedge.LastParameter - nedge.FirstParameter)/2 + midOfArc = nedge.valueAt(midParameter) + nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, + midOfArc, + nedge.Vertexes[0].Point).toShape() + # TODO any better solution than to calculate midpoint + # of arc to reverse? + + elif offsetMode in ["BasewireMode"]: + if (not (curOrientation == firstOrientation) + != (curDir == firstDir)): + if curAlign in ['Left', 'Right']: + # ArchWall has an Offset properties for user to offset + # the basewire before creating the base profile of wall + # (not applicable to 'Center' align) + if basewireOffset: + delta = DraftVecUtils.scaleTo(delta, basewireOffset) + nedge = offset(curredge, delta, trim=True) + else: + nedge = curredge + elif curAlign == 'Center': + delta = delta.negative() + nedge = offset(curredge, delta, trim=True) + else: + if curAlign in ['Left', 'Right']: + # ArchWall has an Offset properties for user to offset + # the basewire before creating the base profile of wall + # (not applicable to 'Center' align) + if basewireOffset: + delta = DraftVecUtils.scaleTo(delta, + delta.Length + basewireOffset) + nedge = offset(curredge, delta, trim=True) + + elif curAlign == 'Center': + nedge = offset(curredge, delta, trim=True) + if curOrientation == "Reversed": + # need to test against Part.Circle, not Part.ArcOfCircle + if not isinstance(curredge.Curve, Part.Circle): + # if not arc/circle, assume straight line, reverse it + nedge = Part.Edge(nedge.Vertexes[1], nedge.Vertexes[0]) + else: + # if arc/circle + # Part.ArcOfCircle(edge.Curve, + # edge.FirstParameter, + # edge.LastParameter, + # edge.Curve.Axis.z > 0) + midParameter = nedge.FirstParameter + (nedge.LastParameter - nedge.FirstParameter)/2 + midOfArc = nedge.valueAt(midParameter) + nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, + midOfArc, + nedge.Vertexes[0].Point).toShape() + # TODO any better solution than to calculate midpoint + # of arc to reverse? + else: + print(" something wrong ") + return None + if not nedge: + return None + + nedges.append(nedge) + + if len(edges) > 1: + nedges = connect(nedges, closed) + else: + nedges = Part.Wire(nedges[0]) + + if bind and not closed: + e1 = Part.LineSegment(edges[0].Vertexes[0].Point, + nedges[0].Vertexes[0].Point).toShape() + e2 = Part.LineSegment(edges[-1].Vertexes[-1].Point, + nedges[-1].Vertexes[-1].Point).toShape() + alledges = edges.extend(nedges) + alledges = alledges.extend([e1, e2]) + w = Part.Wire(alledges) + return w + else: + return nedges