[DraftGeomUtils] - Add per edge Align support

Per edge align feature
- OffsetWire() function is per-edge-aware of aligns information (alignList) for offsettg individual edge
- Instead of using dvec which universally offseting all edge consistently, it calculate vector per edge for offseting
- 2 modes of 'offsetting' to get 2 'offseted' wires from a basewire taking into per-each align info

It accept a normal info to make e.g. all wires in a Sketch gain consistent direction, instead of calculating from indiviual wires
This commit is contained in:
paullee0
2019-12-01 11:29:39 +08:00
committed by Yorik van Havre
parent bcb7f6c871
commit 4818fc4ab4

View File

@@ -1195,26 +1195,47 @@ def calculatePlacement(shape):
pla.Rotation = r
return pla
def offsetWire(wire,dvec,bind=False,occ=False,widthList=None):
def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, alignList=[], normal=None): # 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 offset one are bound by 2 edges, forming a face.
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
each value will be used to offset each corresponding edge in the wire.
(The 1st value override 'dvec' for 1st segment of 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 ?
'''
## TODO In future, 'vector' direction to offset could be 'calculated' in this function - if 'direction' in dvec is not / need not be provided 'outside' the function
## 'dvec' to be obsolete in future ?
# Accept 'wire' as a list of edges, use the list directly, or previously a wire
if isinstance(wire,Part.Wire):
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 direclty 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)
edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges)
norm = getNormal(wire)
closed = isReallyClosed(wire)
nedges = []
if occ:
@@ -1233,48 +1254,170 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None):
# vec of first edge depends on its geometry
e = edges[0]
if isinstance(e.Curve,Part.Circle):
firstVec = e.tangentAt(e.FirstParameter)
else:
firstVec = vec(e)
# 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()
# 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:
print ("alignListC[0] has no value ") # 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)):
curredge = edges[i]
if widthList:
# 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(dvec, widthList[i])
elif widthList[0] > 0:
delta = DraftVecUtils.scaleTo(dvec, widthList[0]) # to follow widthList[0]
# i.e. if widthList[0] == 0, though widthList is not False
# but if dev is provided still, fallback to dvec
delta = DraftVecUtils.scaleTo(delta, widthList[i])
elif dvec:
delta = dvec
else:
return None
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 widthList[0] > 0:
delta = DraftVecUtils.scaleTo(dvec, widthList[0]) # to follow widthList[0]
delta = dvec
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 = dvec
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 'Rigtht' as orginal dvec have set both the direction and amount of offset correct
#elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right':
if i != 0:
if isinstance(curredge.Curve,Part.Circle):
v = curredge.tangentAt(curredge.FirstParameter)
else:
v = vec(curredge)
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)
## TODO - 2019.6.16 - 'calculate' 'offset' direction (in vector) edge by edge instead of rotating previous vector based on dvec in future
# Consider whether generating the 'offset wire' or the 'base wire'
angle = DraftVecUtils.angle(firstVec,v,norm) # use vec deduced depending on geometry instead of - angle = DraftVecUtils.angle(vec(edges[0]),v,norm)
delta = DraftVecUtils.rotate(delta,angle,norm)
if offsetMode == 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..
nedge = offset(curredge,delta,trim=True)
#print("edge ",i,": ",curredge.Curve," ",curredge.Orientation," parameters:",curredge.ParameterRange," vector:",delta)
nedge = offset(curredge,delta,trim=True)
if curOrientation == "Reversed": # TODO arc alway 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']:
nedge = curredge
elif curAlign == 'Center':
delta = delta.negative()
nedge = offset(curredge,delta,trim=True)
else:
if curAlign in ['Left', 'Right']:
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)
@@ -1320,7 +1463,7 @@ def connect(edges,closed=False):
# If the edge pairs has intersection
# ... and if there is prev v2 (prev v2 was calculated intersection), do not calculate again, just use it as current v1 - avoid chance of slight difference in result
# Otherwise, if edge pairs has no intersection (parallel edges, line - arc do no intersect, etc.), so just just current edge endpoints as v1
# And, if edge pairs has no intersection (parallel edges, line - arc do no intersect, etc.), so just just current edge endpoints as v1
# ... and connect these 2 non-intersecting edges
# seem have chance that 2 parallel edges offset same width, result in 2 colinear edges - Wall / DraftGeomUtils seem make them 1 edge and thus 1 vertical plane