[ArchWall] - Add per edge Align feature, and other complementary enabling codes

Per Edge Align Feature
- add OverrideAlign attribute to wall
- to know exactly which Sketch edge sorted into final Wires, so as to know each edge's width / align / etc.
  > sort Sketch edges by sorting Sketch.Geometry instead of Sketch.Shape Edge
  > this ensure consistency in sorting result
- pass widths, align, normal info to revised DraftGeomUtils.py and revised routine to get 2 wires from a basewires

Enabling works
- enable base objects to provide width and align per edge information by getWdiths() and getAligns() methods
- this enable base objects e.g. Sketch, DWire (functions being added) to store per edge info e.g. align, widths etc
  (already can do in SketchObjectPython)
This commit is contained in:
paullee0
2019-12-01 11:11:15 +08:00
committed by Yorik van Havre
parent d7278f8fd0
commit bcb7f6c871

View File

@@ -535,7 +535,10 @@ class _Wall(ArchComponent.Component):
# To be combined into Width when PropertyLengthList is available
if not "OverrideWidth" in lp:
obj.addProperty("App::PropertyFloatList","OverrideWidth","Wall",QT_TRANSLATE_NOOP("App::Property","This override Width attribute to set width of each segment of wall (The 1st value override 'Width' attribute for 1st segment of wall; if a value is zero, 1st value of 'OverrideWidth' will be followed)")) # see DraftGeomUtils.offsetwire()
obj.addProperty("App::PropertyFloatList","OverrideWidth","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Width attribute to set width of each segment of wall. Ignored if Base object provides Widths information, with getWidths() method. (The 1st value override 'Width' attribute for 1st segment of wall; if a value is zero, 1st value of 'OverrideWidth' will be followed)")) # see DraftGeomUtils.offsetwire()
if not "OverrideAlign" in lp:
obj.addProperty("App::PropertyStringList","OverrideAlign","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Align attribute to set Align of each segment of wall. Ignored if Base object provides Aligns information, with getAligns() method. (The 1st value override 'Align' attribute for 1st segment of wall; if a value is not 'Left, Right, Center', 1st value of 'OverrideAlign' will be followed)")) # see DraftGeomUtils.offsetwire()
if not "Height" in lp:
obj.addProperty("App::PropertyLength","Height","Wall",QT_TRANSLATE_NOOP("App::Property","The height of this wall. Keep 0 for automatic. Not used if this wall is based on a solid"))
@@ -821,19 +824,72 @@ class _Wall(ArchComponent.Component):
return data
length = obj.Length.Value
# TODO currently layers were not supported when len(basewires) > 0
# TODO currently layers were not supported when len(basewires) > 0 ##( or 1 ? )
width = 0
widths = obj.OverrideWidth
if obj.OverrideWidth:
if obj.OverrideWidth[0]:
width = obj.OverrideWidth[0]
if not width:
if obj.Width:
width = obj.Width.Value
# Get width of each edge segment from Base Objects if they store it (Adding support in SketchFeaturePython, DWire...)
widths = [] # [] or None are both False
if obj.Base:
if hasattr(obj.Base, 'Proxy'):
if hasattr(obj.Base.Proxy, 'getWidths'):
widths = obj.Base.Proxy.getWidths(obj.Base) # return a list of Width corresponds to indexes of sorted edges of Sketch
# Get width of each edge/wall segment from ArchWall.OverrideWidth if Base Object does not provide it
if not widths:
if obj.OverrideWidth:
if Draft.getType(obj.Base) == 'Sketch':
# If Base Object is ordinary Sketch, sort the width list in OverrrideWidth to correspond to indexes of sorted edges of Sketch
try:
import ArchSketchObject
except:
print("ArchSketchObject add-on module is not installed yet")
try:
widths = ArchSketchObject.sortSketchWidth(obj.Base, obj.OverrideWidth)
except:
widths = obj.OverrideWidth
else:
# If Base Object is not Sketch, but e.g. DWire, the width list in OverrrideWidth just correspond to sequential order of edges
widths = obj.OverrideWidth
elif obj.Width:
widths = [obj.Width.Value]
else:
print("Width or OverrideWidth[0] should not be 0")
return
print ("Width & OverrideWidth & base.getWidths() should not be all 0 or None or [] empty list ")
return None
# set 'default' width - for filling in any item in the list == 0 or None
if obj.Width.Value:
width = obj.Width.Value
else:
width = 200 # 'Default' width value
# Get align of each edge segment from Base Objects if they store it (Adding support in SketchFeaturePython, DWire...)
aligns = []
if obj.Base:
if hasattr(obj.Base, 'Proxy'):
if hasattr(obj.Base.Proxy, 'getAligns'):
aligns = obj.Base.Proxy.getAligns(obj.Base) # return a list of Align corresponds to indexes of sorted edges of Sketch
# Get align of each edge/wall segment from ArchWall.OverrideAlign if Base Object does not provide it
if not aligns:
if obj.OverrideAlign:
if Draft.getType(obj.Base) == 'Sketch':
# If Base Object is ordinary Sketch, sort the align list in OverrideAlign to correspond to indexes of sorted edges of Sketch
try:
import ArchSketchObject
except:
print("ArchSketchObject add-on module is not installed yet")
try:
aligns = ArchSketchObject.sortSketchAlign(obj.Base, obj.OverrideAlign)
except:
aligns = obj.OverrideAlign
else:
# If Base Object is not Sketch, but e.g. DWire, the align list in OverrideAlign just correspond to sequential order of edges
aligns = obj.OverrideAlign
else:
aligns = [obj.Align]
# set 'default' align - for filling in any item in the list == 0 or None
align = obj.Align # or aligns[0]
height = obj.Height.Value
if not height:
@@ -893,34 +949,83 @@ class _Wall(ArchComponent.Component):
return None
else:
base,placement = self.rebase(obj.Base.Shape)
elif len(obj.Base.Shape.Edges) == 1:
self.basewires = [Part.Wire(obj.Base.Shape.Edges)]
# Sort Sketch edges consistently with below procedures without using Sketch.Shape.Edges - found the latter order in some corner case != getSortedClusters()
elif obj.Base.isDerivedFrom("Sketcher::SketchObject"):
self.basewires = []
skGeom = obj.Base.Geometry
skGeomEdges = []
skPlacement = obj.Base.Placement # Get Sketch's placement to restore later
for i in skGeom:
if not i.Construction:
skGeomEdgesI = i.toShape()
skGeomEdges.append(skGeomEdgesI)
for cluster in Part.getSortedClusters(skGeomEdges):
clusterTransformed = []
for edge in cluster:
edge.Placement = edge.Placement.multiply(skPlacement) ## TODO add attribute to skip Transform...
clusterTransformed.append(edge)
self.basewires.append(clusterTransformed) # Only use cluster of edges rather than turning into wire
# Use Sketch's Normal for all edges/wires generated from sketch for consistency
# Discussion on checking normal of sketch.Placement vs sketch.getGlobalPlacement() - https://forum.freecadweb.org/viewtopic.php?f=22&t=39341&p=334275#p334275
# normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1))
else:
# self.basewires = obj.Base.Shape.Wires
self.basewires = []
for cluster in Part.getSortedClusters(obj.Base.Shape.Edges):
for c in Part.sortEdges(cluster):
self.basewires.append(Part.Wire(c))
# if not sketch, e.g. Dwire, can have wire which is 3d so not on the placement's working plane - below applied to Sketch not applicable here
#normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1)) #normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
if self.basewires: # and width: # width already tested earlier...
if (len(self.basewires) == 1) and layers:
self.basewires = [self.basewires[0] for l in layers]
layeroffset = 0
baseface = None
for i,wire in enumerate(self.basewires):
edgeNum = len(wire.Edges)
e = wire.Edges[0]
# Check number of edges per 'wire' and get the 1st edge
if isinstance(wire,Part.Wire):
edgeNum = len(wire.Edges)
e = wire.Edges[0]
elif isinstance(wire[0],Part.Edge):
edgeNum = len(wire)
e = wire[0]
for n in range(0,edgeNum,1): # why these not work - range(edgeNum), range(0,edgeNum) ...
# Fill the aligns list with ArchWall's default align entry and with same number of items as number of edges
try:
if aligns[n] not in ['Left', 'Right', 'Center']:
aligns[n] = align
except:
aligns.append(align)
# Fill the widths List with ArchWall's default width entry and with same number of items as number of edges
try:
if not widths[n]:
widths[n] = width
except:
widths.append(width)
if isinstance(e.Curve,Part.Circle):
dvec = e.Vertexes[0].Point.sub(e.Curve.Center)
else:
dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal)
#dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal)
dvec = DraftGeomUtils.vec(e).cross(normal)
if not DraftVecUtils.isNull(dvec):
dvec.normalize()
sh = None
if obj.Align == "Left":
curAligns = aligns[0]
if curAligns == "Left":
off = obj.Offset.Value
if layers:
off = off+layeroffset
@@ -932,11 +1037,15 @@ class _Wall(ArchComponent.Component):
dvec2 = DraftVecUtils.scaleTo(dvec,off)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, widths)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
# Get the 'offseted' wire taking into account of width and align of each edge
w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal)
# Get the 'base' wire taking into account of width and align of each edge
w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal)
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Right":
elif curAligns == "Right":
dvec = dvec.negative()
off = obj.Offset.Value
if layers:
@@ -949,11 +1058,12 @@ class _Wall(ArchComponent.Component):
dvec2 = DraftVecUtils.scaleTo(dvec,off)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, widths)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal)
w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal)
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Center":
#elif obj.Align == "Center":
elif curAligns == "Center":
if layers:
off = width/2-layeroffset
d1 = Vector(dvec).multiply(off)
@@ -963,18 +1073,13 @@ class _Wall(ArchComponent.Component):
d1 = Vector(dvec).multiply(off)
w2 = DraftGeomUtils.offsetWire(wire,d1)
else:
dvec.multiply(width/2) ## TODO width Value should be of no use (width/2), width Direction remains 'in use'
overrideWidthHalfen = [i/2 for i in widths]
w1 = DraftGeomUtils.offsetWire(wire,dvec,False, False, overrideWidthHalfen)
dvec = dvec.negative()
w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, overrideWidthHalfen)
dvec.multiply(width)
w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal)
w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal)
sh = DraftGeomUtils.bind(w1,w2)
del widths[0:edgeNum]
del aligns[0:edgeNum]
if sh:
sh.fix(0.1,0,1) # fixes self-intersecting wires
f = Part.Face(sh)
@@ -983,34 +1088,25 @@ class _Wall(ArchComponent.Component):
# To allow exportIFC.py to work properly on sketch, which use only 1st face / wire, do not fuse baseface here
# So for a sketch with multiple wires, each returns individual face (rather than fusing together) for exportIFC.py to work properly
# "ArchWall - Based on Sketch Issues" - https://forum.freecadweb.org/viewtopic.php?f=39&t=31235
#
# "Bug #2408: [PartDesign] .fuse is splitting edges it should not"
# - https://forum.freecadweb.org/viewtopic.php?f=10&t=20349&p=346237#p346237
# - bugtracker - https://freecadweb.org/tracker/view.php?id=2408
# Try Part.Shell before removeSplitter
# - https://forum.freecadweb.org/viewtopic.php?f=10&t=20349&start=10
# - 1st finding : if a rectangle + 1 line, can't removesSplitter properly...
# - 2nd finding : if 2 faces do not touch, can't form a shell; then, subsequently for remaining faces even though touch each faces, can't form a shell
baseface.append(f)
# The above make Refine methods below (in else) useless, regardless removeSpitters yet to be improved for cases do not work well
''' Whether layers or not, all baseface.append(f) '''
#if layers:
# if layers[i] >= 0:
# baseface.append(f)
#else:
#baseface = baseface.fuse(f)
#if obj.Refine == 'DraftRemoveSplitter':
# s = DraftGeomUtils.removeSplitter(baseface)
# if s:
# baseface = s
#elif obj.Refine == 'PartRemoveSplitter':
# baseface = baseface.removeSplitter()
else:
baseface = [f]
''' Whether layers or not, all baseface = [f] '''
#if layers:
# if layers[i] >= 0:
# baseface = [f]
#else:
#baseface = f
if baseface:
base,placement = self.rebase(baseface)
else:
@@ -1069,9 +1165,7 @@ class _ViewProviderWall(ArchComponent.ViewProviderComponent):
self.Object = vobj.Object
from pivy import coin
tex = coin.SoTexture2()
image = Draft.loadTexture(Draft.svgpatterns()['simple'][1], 128)
if not image is None:
tex.image = image
tex.image = Draft.loadTexture(Draft.svgpatterns()['simple'][1], 128)
texcoords = coin.SoTextureCoordinatePlane()
s = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("patternScale",0.01)
texcoords.directionS.setValue(s,0,0)