Draft: move functions to draftgeoutils.offsets
This commit is contained in:
@@ -39,6 +39,7 @@ SET (Draft_geoutils
|
||||
draftgeoutils/wires.py
|
||||
draftgeoutils/arcs.py
|
||||
draftgeoutils/fillets.py
|
||||
draftgeoutils/offsets.py
|
||||
)
|
||||
|
||||
SET(Draft_tests
|
||||
|
||||
@@ -107,72 +107,7 @@ from draftgeoutils.intersections import findIntersection
|
||||
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 @@ from draftgeoutils.edges import findMidpoint
|
||||
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 @@ from draftgeoutils.geometry import getRotation
|
||||
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
|
||||
|
||||
486
src/Mod/Draft/draftgeoutils/offsets.py
Normal file
486
src/Mod/Draft/draftgeoutils/offsets.py
Normal file
@@ -0,0 +1,486 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
||||
# * *
|
||||
# * 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
|
||||
Reference in New Issue
Block a user