From 5b0eccac07ebc2195f1c4d8046b68d30cd09f54f Mon Sep 17 00:00:00 2001 From: DeniseBryson Date: Mon, 10 Jun 2024 18:22:51 +0200 Subject: [PATCH] ArchSpace - Correct computed dimensions (floor area and perimeter length) (#14211) * Arch: Improved ArchComponent.computeAreas function All non vertical faces get projected now. Changed Part.Wire to DraftGeomUtils.findWires * Arch: Removed Area property and add AreaCalculationType to Space object The horizontal area and its perimeter are now calculated in two different ways. Eiter at the centre of mass or as a projection on the XY-plane. The results are filled in the filed horizontal area and perimeter in the Component section. * BIM: Added property Area for Spaces again --- src/Mod/BIM/ArchComponent.py | 38 +++++++++++++++++++----------------- src/Mod/BIM/ArchSpace.py | 35 ++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/Mod/BIM/ArchComponent.py b/src/Mod/BIM/ArchComponent.py index 867060fd3c..fdefc6ef96 100644 --- a/src/Mod/BIM/ArchComponent.py +++ b/src/Mod/BIM/ArchComponent.py @@ -994,6 +994,8 @@ class Component(ArchIFC.IfcProduct): import Part import TechDraw + import DraftGeomUtils + fmax = params.get_param_arch("MaxComputeAreas") if len(obj.Shape.Faces) > fmax: obj.VerticalArea = 0 @@ -1015,7 +1017,7 @@ class Component(ArchIFC.IfcProduct): else: if (ang > 1.57) and (ang < 1.571): a += f.Area - if ang < 1.5707: + else: fset.append(f) if a and hasattr(obj,"VerticalArea"): @@ -1024,22 +1026,19 @@ class Component(ArchIFC.IfcProduct): if fset and hasattr(obj,"HorizontalArea"): pset = [] for f in fset: - if f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) < 0.00001: - # already horizontal - pset.append(f) + try: + pf = Part.Face(DraftGeomUtils.findWires(TechDraw.project(f,FreeCAD.Vector(0,0,1))[0].Edges)) + except Part.OCCError: + # error in computing the areas. Better set them to zero than show a wrong value + if obj.HorizontalArea.Value != 0: + print("Debug: Error computing areas for ",obj.Label,": unable to project face: ",str([v.Point for v in f.Vertexes])," (face normal:",f.normalAt(0,0),")") + obj.HorizontalArea = 0 + if hasattr(obj,"PerimeterLength"): + if obj.PerimeterLength.Value != 0: + obj.PerimeterLength = 0 else: - try: - pf = Part.Face(Part.Wire(TechDraw.project(f,FreeCAD.Vector(0,0,1))[0].Edges)) - except Part.OCCError: - # error in computing the areas. Better set them to zero than show a wrong value - if obj.HorizontalArea.Value != 0: - print("Debug: Error computing areas for ",obj.Label,": unable to project face: ",str([v.Point for v in f.Vertexes])," (face normal:",f.normalAt(0,0),")") - obj.HorizontalArea = 0 - if hasattr(obj,"PerimeterLength"): - if obj.PerimeterLength.Value != 0: - obj.PerimeterLength = 0 - else: - pset.append(pf) + pset.append(pf) + if pset: self.flatarea = pset.pop() @@ -1049,8 +1048,11 @@ class Component(ArchIFC.IfcProduct): if obj.HorizontalArea.Value != self.flatarea.Area: obj.HorizontalArea = self.flatarea.Area if hasattr(obj,"PerimeterLength") and (len(self.flatarea.Faces) == 1): - if obj.PerimeterLength.Value != self.flatarea.Faces[0].OuterWire.Length: - obj.PerimeterLength = self.flatarea.Faces[0].OuterWire.Length + edges_table = {} + for e in self.flatarea.Edges: + edges_table.setdefault(e.hashCode(),[]).append(e) + border_edges = [pair[0] for pair in edges_table.values() if len(pair) == 1] + obj.PerimeterLength = sum([e.Length for e in border_edges]) def isStandardCase(self,obj): """Determine if the component is a standard case of its IFC type. diff --git a/src/Mod/BIM/ArchSpace.py b/src/Mod/BIM/ArchSpace.py index 9fe8964967..c43a510f37 100644 --- a/src/Mod/BIM/ArchSpace.py +++ b/src/Mod/BIM/ArchSpace.py @@ -145,6 +145,12 @@ ConditioningTypes = [ "NaturallyVentedOnly" ] +AreaCalculationType = [ + "XY-plane projection", + "At Center of Mass" +] + + import FreeCAD import ArchComponent import ArchCommands @@ -213,7 +219,7 @@ class _Space(ArchComponent.Component): if not "Boundaries" in pl: obj.addProperty("App::PropertyLinkSubList","Boundaries", "Space",QT_TRANSLATE_NOOP("App::Property","The objects that make the boundaries of this space object")) if not "Area" in pl: - obj.addProperty("App::PropertyArea", "Area", "Space",QT_TRANSLATE_NOOP("App::Property","The computed floor area of this space")) + obj.addProperty("App::PropertyArea", "Area", "Space",QT_TRANSLATE_NOOP("App::Property","Identical to Horizontal Area")) if not "FinishFloor" in pl: obj.addProperty("App::PropertyString", "FinishFloor", "Space",QT_TRANSLATE_NOOP("App::Property","The finishing of the floor of this space")) if not "FinishWalls" in pl: @@ -241,8 +247,10 @@ class _Space(ArchComponent.Component): if not "Internal" in pl: obj.addProperty("App::PropertyBool", "Internal", "Space",QT_TRANSLATE_NOOP("App::Property","Specifies if this space is internal or external")) obj.Internal = True + if not "AreaCalculationType" in pl: + obj.addProperty("App::PropertyEnumeration", "AreaCalculationType", "Space",QT_TRANSLATE_NOOP("App::Property","Defines the calculation type for the horizontal area and its perimeter length")) + obj.AreaCalculationType = AreaCalculationType self.Type = "Space" - obj.setEditorMode("HorizontalArea",2) def onDocumentRestored(self,obj): @@ -374,10 +382,14 @@ class _Space(ArchComponent.Component): #print("setting objects shape") shape = shape.Solids[0] self.applyShape(obj,shape,pl) - if hasattr(obj.Area,"Value"): - a = self.getArea(obj) - if obj.Area.Value != a: - obj.Area = a + if hasattr(obj.HorizontalArea,"Value"): + if hasattr(obj,"AreaCalculationType"): + if obj.AreaCalculationType == "At Center of Mass": + a = self.getArea(obj) + obj.HorizontalArea = a + if hasattr(obj,"Area"): + obj.Area = obj.HorizontalArea + return print("Arch: error computing space boundary for",obj.Label) @@ -392,22 +404,13 @@ class _Space(ArchComponent.Component): if hasattr(obj,"PerimeterLength"): if self.face.OuterWire.Length != obj.PerimeterLength.Value: obj.PerimeterLength = self.face.OuterWire.Length - if hasattr(obj,"VerticalArea"): - a = 0 - for f in obj.Shape.Faces: - ang = f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) - if (ang > 1.57) and (ang < 1.571): - a += f.Area - if a != obj.VerticalArea.Value: - obj.VerticalArea = a - #print "area of ",obj.Label," : ",f.Area return self.face.Area else: return 0 def getFootprint(self,obj): - "returns a face that represents the footprint of this space" + "returns a face that represents the footprint of this space at the center of mass" import Part import DraftGeomUtils