From 40600a55c2fe71ff589be677f6e427ccc937d003 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 19 May 2020 13:58:46 +0200 Subject: [PATCH] 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 --- src/Mod/Arch/ArchCurtainWall.py | 100 +++++++++++++++++++++----------- src/Mod/Arch/ArchMaterial.py | 2 +- src/Mod/Arch/ArchWall.py | 72 +++++++++++++---------- 3 files changed, 108 insertions(+), 66 deletions(-) diff --git a/src/Mod/Arch/ArchCurtainWall.py b/src/Mod/Arch/ArchCurtainWall.py index 9190b59105..95f43cd4f7 100644 --- a/src/Mod/Arch/ArchCurtainWall.py +++ b/src/Mod/Arch/ArchCurtainWall.py @@ -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): diff --git a/src/Mod/Arch/ArchMaterial.py b/src/Mod/Arch/ArchMaterial.py index 72580fefd8..de817b83f6 100644 --- a/src/Mod/Arch/ArchMaterial.py +++ b/src/Mod/Arch/ArchMaterial.py @@ -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 diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index bef56798f3..7077da938f 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -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 into a wall object, then uses a 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 @@ -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