Arch: Misc improvements to curtain wall

- Can be based on an edge like normal wall
- Now has a 'host' property to embed into another object (tree only)
- Mullions have different height and width properties
- Mullions or panels can be disabled
This commit is contained in:
Yorik van Havre
2020-05-19 13:58:46 +02:00
parent 6c020f554d
commit 5aa6c4e1da
3 changed files with 108 additions and 66 deletions

View File

@@ -147,12 +147,7 @@ class CommandArchCurtainWall:
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Curtain Wall"))
FreeCADGui.addModule("Draft")
FreeCADGui.addModule("Arch")
FreeCADGui.doCommand("baseline = Draft.makeLine(FreeCAD."+str(self.points[0])+",FreeCAD."+str(self.points[1])+")")
FreeCADGui.doCommand("base = FreeCAD.ActiveDocument.addObject('Part::Extrusion','Extrude')")
FreeCADGui.doCommand("base.Base = baseline")
FreeCADGui.doCommand("base.DirMode = 'Custom'")
FreeCADGui.doCommand("base.Dir = App.Vector(FreeCAD.DraftWorkingPlane.axis)")
FreeCADGui.doCommand("base.LengthFwd = 1000")
FreeCADGui.doCommand("base = Draft.makeLine(FreeCAD."+str(self.points[0])+",FreeCAD."+str(self.points[1])+")")
FreeCADGui.doCommand("obj = Arch.makeCurtainWall(base)")
FreeCADGui.doCommand("Draft.autogroup(obj)")
FreeCAD.ActiveDocument.commitTransaction()
@@ -173,6 +168,15 @@ class CurtainWall(ArchComponent.Component):
def setProperties(self,obj):
pl = obj.PropertiesList
vsize = 50
hsize = 50
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
if not "Host" in pl:
obj.addProperty("App::PropertyLink","Host","CurtainWall",QT_TRANSLATE_NOOP("App::Property","An optional host object for this curtain wall"))
if not "Height" in pl:
obj.addProperty("App::PropertyLength","Height","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The height of the curtain wall, if based on an edge"))
obj.Height = p.GetFloat("WallHeight",3000)
if not "VerticalMullionNumber" in pl:
obj.addProperty("App::PropertyInteger","VerticalMullionNumber","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The number of vertical mullions"))
@@ -183,11 +187,19 @@ class CurtainWall(ArchComponent.Component):
if not "VerticalSections" in pl:
obj.addProperty("App::PropertyInteger","VerticalSections","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The number of vertical sections of this curtain wall"))
obj.VerticalSections = 4
if not "VerticalMullionSize" in pl:
obj.addProperty("App::PropertyLength","VerticalMullionSize","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The size of the vertical mullions, if no profile is used"))
obj.VerticalMullionSize = 100
obj.VerticalSections = 1
if "VerticalMullionSize" in pl:
# obsolete
vsize = obj.VerticalMullionSize.Value
obj.removeProperty("VerticalMullionSize")
if not "VerticalMullionHeight" in pl:
obj.addProperty("App::PropertyLength","VerticalMullionHeight","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The height of the vertical mullions profile, if no profile is used"))
obj.VerticalMullionHeight = vsize
if not "VerticalMullionWidth" in pl:
obj.addProperty("App::PropertyLength","VerticalMullionWidth","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The width of the vertical mullions profile, if no profile is used"))
obj.VerticalMullionWidth = vsize
if not "VerticalMullionProfile" in pl:
obj.addProperty("App::PropertyLink","VerticalMullionProfile","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","A profile for vertical mullions (disables vertical mullion size)"))
@@ -201,11 +213,19 @@ class CurtainWall(ArchComponent.Component):
if not "HorizontalSections" in pl:
obj.addProperty("App::PropertyInteger","HorizontalSections","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The number of horizontal sections of this curtain wall"))
obj.HorizontalSections = 4
if not "HorizontalMullionSize" in pl:
obj.addProperty("App::PropertyLength","HorizontalMullionSize","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The size of the horizontal mullions, if no profile is used"))
obj.HorizontalMullionSize = 50
obj.HorizontalSections = 1
if "HorizontalMullionSize" in pl:
# obsolete
hsize = obj.HorizontalMullionSize.Value
obj.removeProperty("HorizontalMullionSize")
if not "HorizontalMullionHeight" in pl:
obj.addProperty("App::PropertyLength","HorizontalMullionHeight","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The height of the horizontal mullions profile, if no profile is used"))
obj.HorizontalMullionHeight = hsize
if not "HorizontalMullionWidth" in pl:
obj.addProperty("App::PropertyLength","HorizontalMullionWidth","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The width of the horizontal mullions profile, if no profile is used"))
obj.HorizontalMullionWidth = hsize
if not "HorizontalMullionProfile" in pl:
obj.addProperty("App::PropertyLink","HorizontalMullionProfile","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","A profile for horizontal mullions (disables horizontal mullion size)"))
@@ -268,9 +288,6 @@ class CurtainWall(ArchComponent.Component):
if not hasattr(obj.Base,"Shape"):
FreeCAD.Console.PrintLog(obj.Label+": invalid base\n")
return
if not obj.Base.Shape.Faces:
FreeCAD.Console.PrintLog(obj.Label+": no faces in base\n")
return
if obj.VerticalMullionProfile:
if not hasattr(obj.VerticalMullionProfile,"Shape"):
FreeCAD.Console.PrintLog(obj.Label+": invalid vertical mullion profile\n")
@@ -283,13 +300,23 @@ class CurtainWall(ArchComponent.Component):
if not hasattr(obj.DiagonalMullionProfile,"Shape"):
FreeCAD.Console.PrintLog(obj.Label+": invalid diagonal mullion profile\n")
return
if (not obj.HorizontalSections) or (not obj.VerticalSections):
return
facets = []
faces = []
if obj.Base.Shape.Faces:
faces = obj.Base.Shape.Faces
elif obj.Height.Value and obj.VerticalDirection.Length:
ext = FreeCAD.Vector(obj.VerticalDirection)
ext.normalize()
ext = ext.multiply(obj.Height.Value)
faces = [edge.extrude(ext) for edge in obj.Base.Shape.Edges]
if not faces:
FreeCAD.Console.PrintLog(obj.Label+": unable to build base faces\n")
return
# subdivide the faces into quads
for face in obj.Base.Shape.Faces:
for face in faces:
fp = face.ParameterRange
@@ -309,12 +336,16 @@ class CurtainWall(ArchComponent.Component):
vertsec = obj.HorizontalSections
horizsec = obj.VerticalSections
hstep = (fp[1]-fp[0])/vertsec
vstep = (fp[3]-fp[2])/horizsec
hstep = (fp[1]-fp[0])
if vertsec:
hstep = hstep/vertsec
vstep = (fp[3]-fp[2])
if horizsec:
vstep = vstep/horizsec
# construct facets
for i in range(vertsec):
for j in range(horizsec):
for i in range(vertsec or 1):
for j in range(horizsec or 1):
p0 = face.valueAt(fp[0]+i*hstep,fp[2]+j*vstep)
p1 = face.valueAt(fp[0]+(i+1)*hstep,fp[2]+j*vstep)
p2 = face.valueAt(fp[0]+(i+1)*hstep,fp[2]+(j+1)*vstep)
@@ -364,7 +395,7 @@ class CurtainWall(ArchComponent.Component):
# construct vertical mullions
vmullions = []
vprofile = self.getMullionProfile(obj,"Vertical")
if vprofile:
if vprofile and vertsec:
for vedge in vedges:
vn = self.edgenormals[vedge.hashCode()]
if (vn.x != 0) or (vn.y != 0):
@@ -380,7 +411,7 @@ class CurtainWall(ArchComponent.Component):
# construct horizontal mullions
hmullions = []
hprofile = self.getMullionProfile(obj,"Horizontal")
if hprofile:
if hprofile and horizsec:
for hedge in hedges:
rot = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90)
vn = self.edgenormals[hedge.hashCode()]
@@ -493,14 +524,15 @@ class CurtainWall(ArchComponent.Component):
import Part,DraftGeomUtils
prop1 = getattr(obj,direction+"MullionProfile")
prop2 = getattr(obj,direction+"MullionSize").Value
if prop1:
profile = prop1.Shape.copy()
prof = getattr(obj,direction+"MullionProfile")
proh = getattr(obj,direction+"MullionHeight").Value
prow = getattr(obj,direction+"MullionWidth").Value
if prof:
profile = prof.Shape.copy()
else:
if not prop2:
if (not proh) or (not prow):
return None
profile = Part.Face(Part.makePlane(prop2,prop2,FreeCAD.Vector(-prop2/2,-prop2/2,0)))
profile = Part.Face(Part.makePlane(prow,proh,FreeCAD.Vector(-prow/2,-proh/2,0)))
return profile
def getProjectedLength(self,v,ref):

View File

@@ -871,7 +871,7 @@ class _ArchMultiMaterialTaskPanel:
thick = FreeCAD.Units.Quantity(d).Value
else:
thick = FreeCAD.Units.Quantity(d,FreeCAD.Units.Length).Value
th += thick
th += abs(thick)
if not thick:
suffix = " ("+translate("Arch","depends on the object")+")"
val = FreeCAD.Units.Quantity(th,FreeCAD.Units.Length).UserString

View File

@@ -135,7 +135,7 @@ def joinWalls(walls,delete=False):
"""Join the given list of walls into one sketch-based wall.
Take the first wall in the list, and adds on the other walls in the list.
Return the modified first wall.
Return the modified first wall.
Setting delete to True, will delete the other walls. Only join walls
if the walls have the same width, height and alignment.
@@ -188,7 +188,7 @@ def joinWalls(walls,delete=False):
return base
def mergeShapes(w1,w2):
"""Not currently implemented.
"""Not currently implemented.
Return a Shape built on two walls that share same properties and have a
coincident endpoint.
@@ -270,7 +270,7 @@ class _CommandWall:
'ToolTip': QT_TRANSLATE_NOOP("Arch_Wall","Creates a wall object from scratch or from a selected object (wire, face or solid)")}
def IsActive(self):
"""Determines whether or not the Arch Wall tool is active.
"""Determines whether or not the Arch Wall tool is active.
Inactive commands are indicated by a greyed-out icon in the menus and
toolbars.
@@ -579,7 +579,7 @@ class _CommandWall:
FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetInt("WallAlignment",i)
def setContinue(self,i):
"""Simple callback to set if the interactive mode will restart when finished.
"""Simple callback to set if the interactive mode will restart when finished.
This allows for several walls to be placed one after another.
"""
@@ -608,7 +608,7 @@ class _CommandWall:
class _CommandMergeWalls:
"""The command definition for the Arch workbench's gui tool, Arch MergeWalls.
"""The command definition for the Arch workbench's gui tool, Arch MergeWalls.
A tool for merging walls.
@@ -626,7 +626,7 @@ class _CommandMergeWalls:
'ToolTip': QT_TRANSLATE_NOOP("Arch_MergeWalls","Merges the selected walls, if possible")}
def IsActive(self):
"""Determines whether or not the Arch MergeWalls tool is active.
"""Determines whether or not the Arch MergeWalls tool is active.
Inactive commands are indicated by a greyed-out icon in the menus and
toolbars.
@@ -674,7 +674,7 @@ class _CommandMergeWalls:
FreeCAD.ActiveDocument.commitTransaction()
class _Wall(ArchComponent.Component):
"""The Wall object.
"""The Wall object.
Turns a <App::FeaturePython> into a wall object, then uses a
<Part::Feature> to create the wall's shape.
@@ -967,7 +967,7 @@ class _Wall(ArchComponent.Component):
obj.Area = obj.Length.Value * obj.Height.Value
def onBeforeChange(self,obj,prop):
"""Method called before the object has a property changed.
"""Method called before the object has a property changed.
Specifically, this method is called before the value changes.
@@ -1000,8 +1000,8 @@ class _Wall(ArchComponent.Component):
"""
if prop == "Length":
if (obj.Base and obj.Length.Value
and hasattr(self,"oldLength") and (self.oldLength is not None)
if (obj.Base and obj.Length.Value
and hasattr(self,"oldLength") and (self.oldLength is not None)
and (self.oldLength != obj.Length.Value)):
if hasattr(obj.Base,'Shape'):
@@ -1030,7 +1030,7 @@ class _Wall(ArchComponent.Component):
def getFootprint(self,obj):
"""Get the faces that make up the base/foot of the wall.
Returns
-------
list of <Part.Face>
@@ -1047,7 +1047,7 @@ class _Wall(ArchComponent.Component):
def getExtrusionData(self,obj):
"""Get data needed to extrude the wall from a base object.
take the Base object, and find a base face to extrude
out, a vector to define the extrusion direction and distance.
@@ -1086,7 +1086,7 @@ class _Wall(ArchComponent.Component):
if hasattr(obj.Base.Proxy, 'getWidths'):
# Return a list of Width corresponding to indexes of sorted
# edges of Sketch.
widths = obj.Base.Proxy.getWidths(obj.Base)
widths = obj.Base.Proxy.getWidths(obj.Base)
# Get width of each edge/wall segment from ArchWall.OverrideWidth if
# Base Object does not provide it
@@ -1130,7 +1130,7 @@ class _Wall(ArchComponent.Component):
if hasattr(obj.Base.Proxy, 'getAligns'):
# Return a list of Align corresponds to indexes of sorted
# edges of Sketch.
aligns = obj.Base.Proxy.getAligns(obj.Base)
aligns = obj.Base.Proxy.getAligns(obj.Base)
# Get align of each edge/wall segment from ArchWall.OverrideAlign if
# Base Object does not provide it
if not aligns:
@@ -1270,7 +1270,7 @@ class _Wall(ArchComponent.Component):
# 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.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1))
#normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
if self.basewires:
@@ -1325,10 +1325,12 @@ class _Wall(ArchComponent.Component):
if curAligns == "Left":
off = obj.Offset.Value
if layers:
curWidth = abs(layers[i])
off = off+layeroffset
dvec.multiply(abs(layers[i]))
layeroffset += abs(layers[i])
dvec.multiply(curWidth)
layeroffset += abs(curWidth)
else:
curWidth = widths
dvec.multiply(width)
# Now DraftGeomUtils.offsetWire() support
@@ -1344,7 +1346,7 @@ class _Wall(ArchComponent.Component):
w2 = DraftGeomUtils.offsetWire(wire, dvec,
bind=False,
occ=False,
widthList=widths,
widthList=curWidth,
offsetMode=None,
alignList=aligns,
normal=normal,
@@ -1355,7 +1357,7 @@ class _Wall(ArchComponent.Component):
w1 = DraftGeomUtils.offsetWire(wire, dvec,
bind=False,
occ=False,
widthList=widths,
widthList=curWidth,
offsetMode="BasewireMode",
alignList=aligns,
normal=normal,
@@ -1366,10 +1368,12 @@ class _Wall(ArchComponent.Component):
dvec = dvec.negative()
off = obj.Offset.Value
if layers:
curWidth = abs(layers[i])
off = off+layeroffset
dvec.multiply(abs(layers[i]))
layeroffset += abs(layers[i])
dvec.multiply(curWidth)
layeroffset += abs(curWidth)
else:
curWidth = widths
dvec.multiply(width)
# Now DraftGeomUtils.offsetWire() support similar effect as ArchWall Offset
@@ -1381,7 +1385,7 @@ class _Wall(ArchComponent.Component):
w2 = DraftGeomUtils.offsetWire(wire, dvec,
bind=False,
occ=False,
widthList=widths,
widthList=curWidth,
offsetMode=None,
alignList=aligns,
normal=normal,
@@ -1390,8 +1394,8 @@ class _Wall(ArchComponent.Component):
w1 = DraftGeomUtils.offsetWire(wire, dvec,
bind=False,
occ=False,
widthList=widths,
offsetMode="BasewireMode",
widthList=curWidth,
offsetMode=None,
alignList=aligns,
normal=normal,
basewireOffset=off)
@@ -1402,13 +1406,15 @@ class _Wall(ArchComponent.Component):
#elif obj.Align == "Center":
elif curAligns == "Center":
if layers:
off = width/2-layeroffset
totalwidth=sum([abs(l) for l in layers])
curWidth = abs(layers[i])
off = totalwidth/2-layeroffset
d1 = Vector(dvec).multiply(off)
w1 = DraftGeomUtils.offsetWire(wire,d1)
layeroffset += abs(layers[i])
off = width/2-layeroffset
w1 = DraftGeomUtils.offsetWire(wire, d1)
layeroffset += curWidth
off = totalwidth/2-layeroffset
d1 = Vector(dvec).multiply(off)
w2 = DraftGeomUtils.offsetWire(wire,d1)
w2 = DraftGeomUtils.offsetWire(wire, d1)
else:
dvec.multiply(width)
@@ -1427,7 +1433,7 @@ class _Wall(ArchComponent.Component):
offsetMode="BasewireMode",
alignList=aligns,
normal=normal)
sh = DraftGeomUtils.bind(w1,w2)
@@ -1435,6 +1441,10 @@ class _Wall(ArchComponent.Component):
del aligns[0:edgeNum]
if sh:
if layers and (layers[i] < 0):
# layers with negative values are not drawn
continue
sh.fix(0.1,0,1) # fixes self-intersecting wires
f = Part.Face(sh)
@@ -1538,7 +1548,7 @@ class _ViewProviderWall(ArchComponent.ViewProviderComponent):
"""Add display modes' data to the coin scenegraph.
Add each display mode as a coin node, whose parent is this view
provider.
provider.
Each display mode's node includes the data needed to display the object
in that mode. This might include colors of faces, or the draw style of