[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:
committed by
Yorik van Havre
parent
d7278f8fd0
commit
bcb7f6c871
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user