From ec451f9eba9aaf11a7700d0463df1002017eca86 Mon Sep 17 00:00:00 2001 From: Paul Lee Date: Sat, 7 Dec 2024 15:27:35 +0800 Subject: [PATCH] [ArchStructure] Variant PropertrySet Support in ArchSketch Now, user can create : - Variant Layout / combination of ArchStructure (Slab) in addition to ArchWall and CurtainWall based on same ArchSketch, with another PropertySet - Variant Properties / layer of ArchWall on same ArchSketch with different Width, Offset, Material, Height etc. to create Layers, Composite walls etc. (Layout, Layer, Combination, Composite, Elemented etc.) FreeCAD Forum Discussion: - https://forum.freecad.org/viewtopic.php?t=87721&start=130#p794785 --- src/Mod/BIM/ArchStructure.py | 169 ++++++++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 14 deletions(-) diff --git a/src/Mod/BIM/ArchStructure.py b/src/Mod/BIM/ArchStructure.py index 01c560c411..6dae81a430 100644 --- a/src/Mod/BIM/ArchStructure.py +++ b/src/Mod/BIM/ArchStructure.py @@ -715,6 +715,9 @@ class _Structure(ArchComponent.Component): if not "FaceMaker" in pl: obj.addProperty("App::PropertyEnumeration","FaceMaker","Structure",QT_TRANSLATE_NOOP("App::Property","The facemaker type to use to build the profile of this object")) obj.FaceMaker = ["None","Simple","Cheese","Bullseye"] + if not "ArchSketchData" in pl: + obj.addProperty("App::PropertyBool","ArchSketchData","Structure",QT_TRANSLATE_NOOP("App::Property","Use Base ArchSketch (if used) data (e.g. widths, aligns, offsets) instead of Wall's properties")) + obj.ArchSketchData = True if not "ArchSketchEdges" in pl: # PropertyStringList obj.addProperty("App::PropertyStringList","ArchSketchEdges","Structure",QT_TRANSLATE_NOOP("App::Property","Selected edges (or group of edges) of the base ArchSketch, to use in creating the shape of this BIM Structure (instead of using all the Base shape's edges by default). Input are index numbers of edges or groups.")) else: @@ -726,13 +729,59 @@ class _Structure(ArchComponent.Component): obj.removeProperty("ArchSketchEdges") obj.addProperty("App::PropertyStringList","ArchSketchEdges","Structure",QT_TRANSLATE_NOOP("App::Property","Selected edges (or group of edges) of the base ArchSketch, to use in creating the shape of this BIM Structure (instead of using all the Base shape's edges by default). Input are index numbers of edges or groups.")) obj.ArchSketchEdges = newStrValue + if not hasattr(obj,"ArchSketchPropertySet"): + obj.addProperty("App::PropertyEnumeration","ArchSketchPropertySet","Structure",QT_TRANSLATE_NOOP("App::Property","Select User Defined PropertySet to use in creating variant shape, with same ArchSketch ")) + obj.ArchSketchPropertySet = ['Default'] + if not hasattr(self,"ArchSkPropSetPickedUuid"): + self.ArchSkPropSetPickedUuid = '' + if not hasattr(self,"ArchSkPropSetListPrev"): + self.ArchSkPropSetListPrev = [] + self.Type = "Structure" + + def dumps(self): # Supercede Arch.Component.dumps() + dump = super().dumps() + if not isinstance(dump, tuple): + dump = (dump,) #Python Tuple With One Item + dump = dump + (self.ArchSkPropSetPickedUuid, self.ArchSkPropSetListPrev) + return dump + + + def loads(self,state): + super().loads(state) # do nothing as of 2024.11.28 + if state == None: + return + elif state[0] == 'S': # state[1] == 't', behaviour before 2024.11.28 + return + elif state[0] == 'Structure': + self.ArchSkPropSetPickedUuid = state[1] + self.ArchSkPropSetListPrev = state[2] + elif state[0] != 'Structure': # model before merging super.dumps/loads() + self.ArchSkPropSetPickedUuid = state[0] + self.ArchSkPropSetListPrev = state[1] + + def onDocumentRestored(self,obj): ArchComponent.Component.onDocumentRestored(self,obj) self.setProperties(obj) + if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch": + if hasattr(obj,"ArchSketchEdges"): + obj.setEditorMode("ArchSketchEdges", ["ReadOnly"]) + if hasattr(obj,"ArchSketchPropertySet"): + obj.setEditorMode("ArchSketchPropertySet", 0) + else: + if hasattr(obj,"ArchSketchEdges"): + obj.setEditorMode("ArchSketchEdges", 0) + if hasattr(obj,"ArchSketchPropertySet"): + obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"]) + + # set a flag to indicate onDocumentRestored() is run + self.onDocRestoredDone = True + + def execute(self,obj): "creates the structure shape" @@ -746,6 +795,39 @@ class _Structure(ArchComponent.Component): base = None pl = obj.Placement + + # PropertySet support + propSetPickedUuidPrev = self.ArchSkPropSetPickedUuid + propSetListPrev = self.ArchSkPropSetListPrev + propSetSelectedNamePrev = obj.ArchSketchPropertySet + propSetSelectedNameCur = None + propSetListCur = None + if Draft.getType(obj.Base) == "ArchSketch": + baseProxy = obj.Base.Proxy + if hasattr(baseProxy,"getPropertySet"): + # get full list of PropertySet + propSetListCur = baseProxy.getPropertySet(obj.Base) + # get updated name (if any) of the selected PropertySet + propSetSelectedNameCur = baseProxy.getPropertySet(obj.Base, + propSetUuid=propSetPickedUuidPrev) + if propSetSelectedNameCur: # True if selection is not deleted + if propSetListPrev != propSetListCur: + obj.ArchSketchPropertySet = propSetListCur + obj.ArchSketchPropertySet = propSetSelectedNameCur + self.ArchSkPropSetListPrev = propSetListCur + #elif propSetListPrev == propSetListCur: + #pass #nothing to do in this case + # but if below, though (propSetListPrev == propSetListCur) + elif propSetSelectedNamePrev != propSetSelectedNameCur: + obj.ArchSketchPropertySet = propSetSelectedNameCur + else: # True if selection is deleted + if propSetListCur: + if propSetListPrev != propSetListCur: + obj.ArchSketchPropertySet = propSetListCur + obj.ArchSketchPropertySet = 'Default' + #else: # Seems no need ... + #obj.PropertySet = 'Default' + extdata = self.getExtrusionData(obj) if extdata: @@ -851,19 +933,52 @@ class _Structure(ArchComponent.Component): # ArchSketch feature : # Get base shape wires, and faceMaker, for Structure (slab. etc.) from Base Objects if they store and provide by getStructureBaseShapeWires() # (thickness, normal/extrusion, length, width, baseface maybe for later) of structure (slab etc.) - structureBaseShapeWires = None - baseShapeWires = None #baseSlabWires / baseSlabOpeningWires = None + structureBaseShapeWires = [] + baseShapeWires = [] #baseSlabWires / baseSlabOpeningWires = None faceMaker = None - if hasattr(obj.Base, 'Proxy'): - if hasattr(obj.Base.Proxy, 'getStructureBaseShapeWires'): - structureBaseShapeWires = obj.Base.Proxy.getStructureBaseShapeWires(obj.Base, archsketchEdges=obj.ArchSketchEdges) - # provide selected edges, or groups, in obj.ArchSketchEdges for processing in getStructureBaseShapeWires() (getSortedClusters) as override - # returned a {dict} ( or a [list] ) - # get slab wires; use original wires if structureBaseShapeWires() provided none - if structureBaseShapeWires: # would be false (none) if both base ArchSketch and obj do not have the edges stored / inputted by user - # if structureBaseShapeWires is {dict} - baseShapeWires = structureBaseShapeWires.get('slabWires') - faceMaker = structureBaseShapeWires.get('faceMaker') + + if hasattr(obj.Base, 'Proxy') and obj.ArchSketchData and \ + hasattr(obj.Base.Proxy, 'getStructureBaseShapeWires'): + propSetUuid = self.ArchSkPropSetPickedUuid + + # provide selected edges, or groups, in obj.ArchSketchEdges for processing in getStructureBaseShapeWires() (getSortedClusters) as override + structureBaseShapeWires = obj.Base.Proxy.getStructureBaseShapeWires(obj.Base, + propSetUuid=propSetUuid) + # get slab wires; use original wires if structureBaseShapeWires() provided none + if structureBaseShapeWires: # would be false (none) if both base ArchSketch and obj do not have the edges stored / inputted by user + # if structureBaseShapeWires is {dict} + baseShapeWires = structureBaseShapeWires.get('slabWires') + faceMaker = structureBaseShapeWires.get('faceMaker') + elif obj.Base.isDerivedFrom("Sketcher::SketchObject"): + skGeom = obj.Base.GeometryFacadeList + skGeomEdges = [] + skPlacement = obj.Base.Placement # Get Sketch's placement to restore later + # Get ArchSketch edges to construct ArchStructure + # No need to test obj.ArchSketchData ... + for ig, geom in enumerate(skGeom): + # Construction mode edges should be ignored if + # ArchSketchEdges, otherwise, ArchSketchEdges data + # needs to take out those in Construction before + # using as parameters. + if (not obj.ArchSketchEdges and not geom.Construction) or str(ig) in obj.ArchSketchEdges: + # support Line, Arc, Circle, Ellipse for Sketch + # as Base at the moment + if isinstance(geom.Geometry, (Part.LineSegment, + Part.Circle, Part.ArcOfCircle, + Part.Ellipse)): + skGeomEdgesI = geom.Geometry.toShape() + skGeomEdges.append(skGeomEdgesI) + clusterTransformed = [] + for cluster in Part.getSortedClusters(skGeomEdges): + edgesTransformed = [] + for edge in cluster: + edge.Placement = edge.Placement.multiply(skPlacement) + edgesTransformed.append(edge) + clusterTransformed.append(edgesTransformed) + for clusterT in clusterTransformed: + baseShapeWires.append(Part.Wire(clusterT)) + faceMaker = 'Bullseye' + if not baseShapeWires: baseShapeWires = obj.Base.Shape.Wires if faceMaker or (obj.FaceMaker != "None"): @@ -936,14 +1051,14 @@ class _Structure(ArchComponent.Component): if obj.Normal.Length: normal = Vector(obj.Normal).normalize() else: - normal = baseface.Faces[0].normalAt(0, 0) ## TODO to use ArchSketch's 'normal' for consistency + normal = baseface.Faces[0].normalAt(0, 0) # TODO to use ArchSketch's 'normal' for consistency base = None placement = None inverse_placement = None if len(baseface.Faces) > 1: base = [] placement = [] - hint = baseface.Faces[0].normalAt(0, 0) ## TODO anything to do ? + hint = baseface.Faces[0].normalAt(0, 0) # TODO anything to do ? for f in baseface.Faces: bf, pf = self.rebase(f, hint) base.append(bf) @@ -976,6 +1091,12 @@ class _Structure(ArchComponent.Component): def onChanged(self,obj,prop): + # check the flag indicating if onDocumentRestored() has been run; if + # not, no further code is run - as getExtrusionData() below return + # error when some properties are not added by onDocumentRestored() + if not hasattr(self,"onDocRestoredDone"): + return + if hasattr(obj,"IfcType"): IfcType = obj.IfcType else: @@ -1017,6 +1138,26 @@ class _Structure(ArchComponent.Component): obj.Nodes = self.nodes ArchComponent.Component.onChanged(self,obj,prop) + if (prop == "ArchSketchPropertySet" + and Draft.getType(obj.Base) == "ArchSketch"): + baseProxy = obj.Base.Proxy + if hasattr(baseProxy,"getPropertySet"): + uuid = baseProxy.getPropertySet(obj, + propSetName=obj.ArchSketchPropertySet) + self.ArchSkPropSetPickedUuid = uuid + if (hasattr(obj,"ArchSketchData") and obj.ArchSketchData + and Draft.getType(obj.Base) == "ArchSketch"): + if hasattr(obj,"ArchSketchEdges"): + obj.setEditorMode("ArchSketchEdges", ["ReadOnly"]) + if hasattr(obj,"ArchSketchPropertySet"): + obj.setEditorMode("ArchSketchPropertySet", 0) + else: + if hasattr(obj,"ArchSketchEdges"): + obj.setEditorMode("ArchSketchEdges", 0) + if hasattr(obj,"ArchSketchPropertySet"): + obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"]) + + def getNodeEdges(self,obj): "returns a list of edges from structural nodes"