From 56c01f17fa9e8c63cbbb7b31d79f2454fd9cfd55 Mon Sep 17 00:00:00 2001 From: David Daish Date: Tue, 31 Mar 2020 21:02:24 +1300 Subject: [PATCH] Best efforts on arch wall for now. --- src/Mod/Arch/ArchWall.py | 253 ++++++++++++++++++++++++++++++--------- 1 file changed, 199 insertions(+), 54 deletions(-) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index ab55d29a44..7086b2ad41 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -39,24 +39,28 @@ else: # \ingroup ARCH # \brief The Wall object and tools # -# This module provides tools to build Wall objects. -# Walls are simple objects, usually vertical, obtained -# by giving a thickness to a base line, then extruding it -# vertically. +# This module provides tools to build Wall objects. Walls are simple objects, +# usually vertical, typically obtained by giving a thickness to a base line, +# then extruding it vertically. __title__="FreeCAD Wall" __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" +__doc__="""This module provides tools to build Wall objects. Walls are simple +objects, usually vertical, typically obtained by giving a thickness to a base +line, then extruding it vertically. +Examples +-------- +TODO put examples here. + +""" def makeWall(baseobj=None,height=None,length=None,width=None,align="Center",face=None,name="Wall"): '''Creates a wall based on a given object, and returns the generated wall. -Notes ------ -TODO: It is unclear what defines which units this function uses, or what -defines which units this function uses. +TODO: It is unclear what defines which units this function uses. Parameters ---------- @@ -81,8 +85,18 @@ name: str, optional Returns ------- - + Returns the generated wall. + +Notes +----- +Creates a new object, and turns it into a parametric wall +object. This object does not yet have any shape. + +The wall then uses the baseobj.Shape as the basis to extrude out a wall shape, +giving the new object a shape. + +It then hides the original baseobj. ''' if not FreeCAD.ActiveDocument: @@ -128,7 +142,7 @@ the walls have the same width, height and alignment. Parameters ---------- -walls: list of +walls: list of List containing the walls to add to the first wall in the list. Walls must be based off a base object. delete: bool, optional @@ -136,7 +150,7 @@ delete: bool, optional Returns ------- - + """ import Part @@ -237,7 +251,8 @@ class _CommandWall: """The command definition for the Arch workbench's gui tool, Arch Wall. A tool for creating Arch walls. Creates a wall from the object selected by the user. If no objects are -selected, enters interactive mode to create a wall using selected points. +selected, enters an interactive mode to create a wall using selected points +to create a base. Find documentation on the end user usage of Arch Wall here: https://wiki.freecadweb.org/Arch_Wall @@ -263,7 +278,7 @@ https://wiki.freecadweb.org/Arch_Wall """Executed when Arch Wall is called. Creates a wall from the object selected by the user. If no objects are -selected, enters interactive mode to create a wall using selected points +selected, enters an interactive mode to create a wall using selected points to create a base. """ @@ -321,7 +336,7 @@ to create a base. def getPoint(self,point=None,obj=None): """Callback for clicks during interactive mode. -When method _CommandWall.Activated() has entered the interactive mode this +When method _CommandWall.Activated() has entered the interactive mode, this callback runs when the user clicks. Parameters @@ -444,8 +459,7 @@ point: self.Length.setText(FreeCAD.Units.Quantity(bv.Length,FreeCAD.Units.Length).UserString) def taskbox(self): - - "sets up a taskbox widget" + """Sets up a simple gui widget for the interactive mode.""" w = QtGui.QWidget() ui = FreeCADGui.UiLoader() @@ -529,6 +543,7 @@ point: return w def setMat(self,d): + """Simple callback for the interactive mode gui widget to set material.""" if d == 0: self.MultiMat = None @@ -538,10 +553,12 @@ point: FreeCAD.LastArchMultiMaterial = self.MultiMat.Name def setLength(self,d): + """Simple callback for the interactive mode gui widget to set length.""" self.lengthValue = d def setWidth(self,d): + """Simple callback for the interactive mode gui widget to set width.""" self.Width = d self.tracker.width(d) @@ -549,27 +566,35 @@ point: def setHeight(self,d): + """Simple callback for the interactive mode gui widget to set height.""" self.Height = d self.tracker.height(d) FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("WallHeight",d) def setAlign(self,i): + """Simple callback for the interactive mode gui widget to set alignment.""" self.Align = ["Center","Left","Right"][i] 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. + +This allows for several walls to be placed one after another. +""" self.continueCmd = bool(i) if hasattr(FreeCADGui,"draftToolBar"): FreeCADGui.draftToolBar.continueMode = bool(i) def setUseSketch(self,i): + """Simple callback to set if walls should join their base sketches when possible.""" - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetBool("WallSketches",bool(i)) + FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetBool("joinWallSketches",bool(i)) def createFromGUI(self): + """Callback to create wall by using the _CommandWall.taskbox()""" FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Wall")) FreeCADGui.addModule("Arch") @@ -582,23 +607,38 @@ point: FreeCADGui.draftToolBar.escape() - class _CommandMergeWalls: + """The command definition for the Arch workbench's gui tool, Arch MergeWalls. A tool for merging walls. - "the Arch Merge Walls command definition" +Joins two or more walls by using the ArchWall.joinWalls() function. + +Find documentation on the end user usage of Arch Wall here: +https://wiki.freecadweb.org/Arch_MergeWalls +""" def GetResources(self): + """Returns a dictonary with the visual aspects of the Arch MergeWalls tool.""" return {'Pixmap' : 'Arch_MergeWalls', 'MenuText': QT_TRANSLATE_NOOP("Arch_MergeWalls","Merge Walls"), '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. + + Inactive commands are indicated by a greyed-out icon in the menus and toolbars. + """ return bool(FreeCADGui.Selection.getSelection()) def Activated(self): + """Executed when Arch MergeWalls is called. +Calls ArchWall.joinWalls() on walls selected by the user, with the delete +option enabled. If the user has selected a single wall, check to see if the +wall has any Additions that are walls. If so, merges these additions to the +wall, deleting the additions. +""" walls = FreeCADGui.Selection.getSelection() if len(walls) == 1: if Draft.getType(walls[0]) == "Wall": @@ -629,19 +669,39 @@ class _CommandMergeWalls: FreeCADGui.doCommand("Arch.joinWalls(FreeCADGui.Selection.getSelection(),delete=True)") FreeCAD.ActiveDocument.commitTransaction() - - class _Wall(ArchComponent.Component): + """The Wall object. Takes a and turns it into a wall. - "The Wall object" +Walls are simple objects, usually vertical, typically obtained by giving a +thickness to a base line, then extruding it vertically. + +Parameters +---------- +obj: + The object to turn into a wall. Note that this is not the object that forms + the basis for the new wall's shape. That is given later. +""" def __init__(self, obj): + """Initialises the object's properties. + +Sets the object to have the properties of an Arch component, and Arch wall. +""" ArchComponent.Component.__init__(self, obj) self.setProperties(obj) obj.IfcType = "Wall" def setProperties(self, obj): + """Gives the wall it's wall specific properties, such as it's alignment. + +You can learn more about properties here: https://wiki.freecadweb.org/property + +parameters +---------- +obj: + The object to turn into a wall. +""" lp = obj.PropertiesList if not "Length" in lp: @@ -698,6 +758,7 @@ class _Wall(ArchComponent.Component): self.Type = "Wall" def onDocumentRestored(self,obj): + """Method run when the document is restored. Re-adds the Arch component, and Arch wall properties.""" ArchComponent.Component.onDocumentRestored(self,obj) self.setProperties(obj) @@ -938,6 +999,9 @@ class _Wall(ArchComponent.Component): """returns (shape,extrusion vector,placement) or None""" import Part,DraftGeomUtils + + # If ArchComponent.Component.getExtrusionData() can successfully get + # extrusion data, just use that. data = ArchComponent.Component.getExtrusionData(self,obj) if data: if not isinstance(data[0],list): @@ -948,17 +1012,19 @@ class _Wall(ArchComponent.Component): # TODO currently layers were not supported when len(basewires) > 0 ##( or 1 ? ) width = 0 - # Get width of each edge segment from Base Objects if they store it (Adding support in SketchFeaturePython, DWire...) + # 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 + # Return a list of Width corresponding to indexes of sorted + # edges of Sketch. + widths = obj.Base.Proxy.getWidths(obj.Base) + # Get width of each edge/wall segment from ArchWall.OverrideWidth if + # Base Object does not provide it if not widths: - if obj.OverrideWidth: if obj.Base.isDerivedFrom("Sketcher::SketchObject"): # If Base Object is ordinary Sketch (or when ArchSketch.getWidth() not implemented yet):- @@ -972,7 +1038,9 @@ class _Wall(ArchComponent.Component): 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 + # 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] @@ -980,25 +1048,30 @@ class _Wall(ArchComponent.Component): 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 + # 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...) + # 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 + # Return a list of Align corresponds to indexes of sorted + # edges of Sketch. + 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: if obj.OverrideAlign: if obj.Base.isDerivedFrom("Sketcher::SketchObject"): - # If Base Object is ordinary Sketch (or when ArchSketch.getAligns() not implemented yet):- - # sort the align list in OverrideAlign to correspond to indexes of sorted edges of Sketch + # If Base Object is ordinary Sketch (or when + # ArchSketch.getAligns() not implemented yet):- sort the + # align list in OverrideAlign to correspond to indexes of + # sorted edges of Sketch try: import ArchSketchObject except: @@ -1008,7 +1081,9 @@ class _Wall(ArchComponent.Component): 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 + # 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] @@ -1078,7 +1153,9 @@ class _Wall(ArchComponent.Component): 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() + # 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 @@ -1093,9 +1170,14 @@ class _Wall(ArchComponent.Component): 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 + # Only use cluster of edges rather than turning into wire + self.basewires.append(clusterTransformed) + + # 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)) @@ -1105,8 +1187,11 @@ class _Wall(ArchComponent.Component): 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 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: @@ -1125,14 +1210,18 @@ class _Wall(ArchComponent.Component): 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 + # 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 + # 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 @@ -1165,11 +1254,32 @@ class _Wall(ArchComponent.Component): # dvec2 = DraftVecUtils.scaleTo(dvec,off) # wire = DraftGeomUtils.offsetWire(wire,dvec2) - # Get the 'offseted' wire taking into account of Width and Align of each edge, and overall Offset - w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal,off) + # Get the 'offseted' wire taking into account + # of Width and Align of each edge, and overall + # Offset + w2 = DraftGeomUtils.offsetWire( + wire,dvec, + bind=False, + occ=False, + widthList=widths, + offsetMode=None, + alignList=aligns, + normal=normal, + basewireOffset=off + ) - # 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,off) + # Get the 'base' wire taking into account of + # width and align of each edge + w1 = DraftGeomUtils.offsetWire( + wire, dvec, + bind=False, + occ=False, + widthList=widths, + offsetMode="BasewireMode", + alignList=aligns, + normal=normal, + basewireOffset=off + ) sh = DraftGeomUtils.bind(w1,w2) elif curAligns == "Right": @@ -1188,10 +1298,24 @@ class _Wall(ArchComponent.Component): # dvec2 = DraftVecUtils.scaleTo(dvec,off) # wire = DraftGeomUtils.offsetWire(wire,dvec2) - w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal,off) - w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal,off) + w2 = DraftGeomUtils.offsetWire( + wire, dvec, + bind=False, + occ=False, + widthList=widths, + offsetMode=None, + alignList=aligns, + normal=normal, + basewireOffset=off + ) + + w1 = DraftGeomUtils.offsetWire( + basewireOffset=off + ) + sh = DraftGeomUtils.bind(w1,w2) + #elif obj.Align == "Center": elif curAligns == "Center": if layers: @@ -1204,8 +1328,24 @@ class _Wall(ArchComponent.Component): w2 = DraftGeomUtils.offsetWire(wire,d1) else: 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) + w2 = DraftGeomUtils.offsetWire(wire, + wire, dvec, + bind=False, + occ=False, + widthList=widths, + offsetMode=None, + alignList=aligns, + normal=normal + ) + w1 = DraftGeomUtils.offsetWire(wire, + wire, dvec, + bind=False, + occ=False, + widthList=widths, + offsetMode="BasewireMode", + alignList=aligns, + normal=normal + ) sh = DraftGeomUtils.bind(w1,w2) del widths[0:edgeNum] @@ -1217,8 +1357,13 @@ class _Wall(ArchComponent.Component): f = Part.Face(sh) if baseface: - # 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 + # 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"