diff --git a/src/Mod/Arch/CMakeLists.txt b/src/Mod/Arch/CMakeLists.txt
index 151071742d..1ed820e16e 100644
--- a/src/Mod/Arch/CMakeLists.txt
+++ b/src/Mod/Arch/CMakeLists.txt
@@ -54,6 +54,7 @@ SET(Arch_SRCS
ArchTruss.py
ArchCurtainWall.py
importSHP.py
+ exportIFCStructuralTools.py
)
SET(Dice3DS_SRCS
diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui b/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui
index 5706fac8f6..b7a136d9f5 100644
--- a/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui
+++ b/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui
@@ -39,6 +39,45 @@
Export options
+ -
+
+
-
+
+
+ Export type
+
+
+
+ -
+
+
+ The type of objects you wish to export: Standard (solid objects), wireframe model for structural analysis, or both in a same model
+
+
+ ifcExportModel
+
+
+ Mod/Arch
+
+
-
+
+ Standard model
+
+
+ -
+
+ Structural analysis
+
+
+ -
+
+ Standard + structural
+
+
+
+
+
+
-
diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py
index f9ecbb88a3..35b8005237 100644
--- a/src/Mod/Arch/exportIFC.py
+++ b/src/Mod/Arch/exportIFC.py
@@ -38,6 +38,7 @@ import Arch
import DraftVecUtils
import ArchIFCSchema
import exportIFCHelper
+import exportIFCStructuralTools
from DraftGeomUtils import vec
from importIFCHelper import dd2dms
@@ -140,7 +141,8 @@ def getPreferences():
'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True),
'IFC_UNIT': u,
'SCALE_FACTOR': f,
- 'GET_STANDARD': p.GetBool("getStandardType",False)
+ 'GET_STANDARD': p.GetBool("getStandardType",False),
+ 'EXPORT_MODEL': ['arch','struct','hybrid'][p.GetInt("ifcExportModel",0)]
}
if hasattr(ifcopenshell,"schema_identifier"):
schema = ifcopenshell.schema_identifier
@@ -228,7 +230,7 @@ def export(exportList,filename,colors=None,preferences=None):
if preferences['FULL_PARAMETRIC']:
objectslist = Arch.getAllChildren(objectslist)
- # create project and context
+ # create project, context and geodata settings
contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist)
context = contextCreator.model_view_subcontext
@@ -239,6 +241,16 @@ def export(exportList,filename,colors=None,preferences=None):
decl = Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian)
contextCreator.model_context.TrueNorth.DirectionRatios = (math.cos(decl+math.pi/2), math.sin(decl+math.pi/2))
+ # reusable entity system
+
+ global ifcbin
+ ifcbin = exportIFCHelper.recycler(ifcfile)
+
+ # setup analytic model
+
+ if preferences['EXPORT_MODEL'] in ['struct','hybrid']:
+ exportIFCStructuralTools.setup(ifcfile,ifcbin,preferences['SCALE_FACTOR'])
+
# define holders for the different types we create
products = {} # { Name: IfcEntity, ... }
@@ -252,11 +264,6 @@ def export(exportList,filename,colors=None,preferences=None):
shapedefs = {} # { ShapeDefString:[shapes],... }
spatialelements = {} # {Name:IfcEntity, ... }
- # reusable entity system
-
- global ifcbin
- ifcbin = exportIFCHelper.recycler(ifcfile)
-
# build clones table
if preferences['CREATE_CLONES']:
@@ -279,6 +286,14 @@ def export(exportList,filename,colors=None,preferences=None):
for obj in objectslist:
+ # structural analysis object
+
+ structobj = None
+ if preferences['EXPORT_MODEL'] in ['struct','hybrid']:
+ structobj = exportIFCStructuralTools.createStructuralMember(ifcfile,ifcbin,obj)
+ if preferences['EXPORT_MODEL'] == 'struct':
+ continue
+
# getting generic data
name = getText("Name",obj)
@@ -453,6 +468,11 @@ def export(exportList,filename,colors=None,preferences=None):
if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]:
spatialelements[obj.Name] = product
+ # associate with structural analysis object if any
+
+ if structobj:
+ exportIFCStructuralTools.associates(ifcfile,product,structobj)
+
# gather assembly subelements
if assemblyElements:
@@ -834,6 +854,11 @@ def export(exportList,filename,colors=None,preferences=None):
count += 1
+ # relate structural analysis objects to the struct model
+
+ if preferences['EXPORT_MODEL'] in ['struct','hybrid']:
+ exportIFCStructuralTools.createStructuralGroup(ifcfile)
+
# relationships
sites = []
@@ -2012,11 +2037,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
productdef = ifcfile.add(p)
for rep in productdef.Representations:
rep.ContextOfItems = context
- xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
- zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
- ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
- gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
- placement = ifcbin.createIfcLocalPlacement(gpl)
+ placement = ifcbin.createIfcLocalPlacement()
shapetype = "advancedbrep"
shapes = None
serialized = True
@@ -2124,10 +2145,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
colorshapes = shapes # to keep track of individual shapes for coloring below
if tostore:
subrep = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
- xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
- zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
- ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
- gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
+ gpl = ifcbin.createIfcAxis2Placement3D()
repmap = ifcfile.createIfcRepresentationMap(gpl,subrep)
pla = obj.getGlobalPlacement()
axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
@@ -2194,11 +2212,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
surfstyles[key] = psa
isi = ifcfile.createIfcStyledItem(shape,[psa],None)
- xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
- zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
- ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
- gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
- placement = ifcbin.createIfcLocalPlacement(gpl)
+ placement = ifcbin.createIfcLocalPlacement()
representation = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation])
diff --git a/src/Mod/Arch/exportIFCHelper.py b/src/Mod/Arch/exportIFCHelper.py
index 15fc89798a..4b8c98cff7 100644
--- a/src/Mod/Arch/exportIFCHelper.py
+++ b/src/Mod/Arch/exportIFCHelper.py
@@ -280,7 +280,11 @@ class recycler:
self.propertysinglevalues[key] = c
return c
- def createIfcAxis2Placement3D(self,p1,p2,p3):
+ def createIfcAxis2Placement3D(self,p1=None,p2=None,p3=None):
+ if not p1:
+ p1 = self.createIfcCartesianPoint((0.0,0.0,0.0))
+ p2 = self.createIfcDirection((0.0,0.0,1.0))
+ p3 = self.createIfcDirection((1.0,0.0,0.0))
if p2:
tp2 = str(p2.DirectionRatios)
else:
@@ -310,7 +314,9 @@ class recycler:
self.axis2placement2ds[key] = c
return c
- def createIfcLocalPlacement(self,gpl):
+ def createIfcLocalPlacement(self,gpl=None):
+ if not gpl:
+ gpl = self.createIfcAxis2Placement3D()
key = str(gpl.Location.Coordinates) + str(gpl.Axis.DirectionRatios) + str(gpl.RefDirection.DirectionRatios)
if self.compress and key in self.localplacements:
self.spared += 1
diff --git a/src/Mod/Arch/exportIFCStructuralTools.py b/src/Mod/Arch/exportIFCStructuralTools.py
new file mode 100644
index 0000000000..e04da9a665
--- /dev/null
+++ b/src/Mod/Arch/exportIFCStructuralTools.py
@@ -0,0 +1,180 @@
+# ***************************************************************************
+# * Copyright (c) 2020 Yorik van Havre *
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of the GNU Lesser General Public License (LGPL) *
+# * as published by the Free Software Foundation; either version 2 of *
+# * the License, or (at your option) any later version. *
+# * for detail see the LICENCE text file. *
+# * *
+# * This program is distributed in the hope that it will be useful, *
+# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+# * GNU Library General Public License for more details. *
+# * *
+# * You should have received a copy of the GNU Library General Public *
+# * License along with this program; if not, write to the Free Software *
+# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+# * USA *
+# * *
+# ***************************************************************************
+
+from __future__ import print_function
+
+
+__title__ = "FreeCAD structural IFC export tools"
+__author__ = "Yorik van Havre"
+__url__ = "https://www.freecadweb.org"
+
+ALLOW_LINEAR_OBJECTS = True # allow non-solid objects (wires, etc) to become analytic objects?
+
+structural_nodes = {} # this keeps track of nodes during this session
+scaling = 1.0 # this keeps track of scaling during this session
+
+
+def setup(ifcfile,ifcbin,scale):
+
+ """Creates all the needed setup for structural model."""
+
+ global structural_nodes,scaling
+ structural_nodes = {}
+ scaling = scale
+ import ifcopenshell
+ uid = ifcopenshell.guid.new
+ owh = ifcfile.by_type("IfcOwnerHistory")[0]
+ prj = ifcfile.by_type("IfcProject")[0]
+ ctx = createStructuralContext(ifcfile)
+ if ifcfile.wrapped_data.schema_name == "IFC2X3":
+ mod = ifcfile.createIfcStructuralAnalysisModel(uid(),owh,"Structural Analysis Model",None,None,"NOTDEFINED",None,None,None)
+ else:
+ pla = ifcbin.createIfcLocalPlacement()
+ mod = ifcfile.createIfcStructuralAnalysisModel(uid(),owh,"Structural Analysis Model",None,None,"NOTDEFINED",None,None,None,pla)
+ rel = ifcfile.createIfcRelDeclares(uid(),owh,None,None,prj,[mod])
+
+
+def createStructuralContext(ifcfile):
+
+ """Creates an additional geometry context for structural objects. Returns the new context"""
+
+ contexts = ifcfile.by_type("IfcGeometricRepresentationContext")
+ # filter out subcontexts
+ contexts = [c for c in contexts if c.is_a() == "IfcGeometricRepresentationContext"]
+ ctx = contexts[0] # arbitrarily take the first one...
+ structcontext = ifcfile.createIfcGeometricRepresentationSubContext('Analysis','Axis',None,None,None,None,ctx,None,"GRAPH_VIEW",None)
+ return structcontext
+
+
+def getStructuralContext(ifcfile):
+
+ """Returns the structural context from the file"""
+ for c in ifcfile.by_type("IfcGeometricRepresentationSubContext"):
+ if c.ContextIdentifier == "Analysis":
+ return c
+
+
+def createStructuralNode(ifcfile,ifcbin,point):
+
+ """Creates a connection node at the given point"""
+
+ import ifcopenshell
+ uid = ifcopenshell.guid.new
+ owh = ifcfile.by_type("IfcOwnerHistory")[0]
+ ctx = getStructuralContext(ifcfile)
+ cpt = ifcbin.createIfcCartesianPoint(tuple(point))
+ vtx = ifcfile.createIfcVertexPoint(cpt)
+ rep = ifcfile.createIfcTopologyRepresentation(ctx,'Analysis','Vertex',[vtx])
+ psh = ifcfile.createIfcProductDefinitionShape(None,None,[rep])
+ # boundary conditions serve for ex. to create fixed nodes
+ #cnd = ifcfile.createIfcBoundaryNodeCondition("Fixed",ifcfile.createIfcBoolean(True),ifcfile.createIfcBoolean(True),ifcfile.createIfcBoolean(True),ifcfile.createIfcBoolean(True),ifcfile.createIfcBoolean(True),ifcfile.createIfcBoolean(True))
+ # for now we don't create any boundary condition
+ cnd = None
+ pla = ifcbin.createIfcLocalPlacement()
+ prd = ifcfile.createIfcStructuralPointConnection(uid(),owh,'Vertex',None,None,pla,psh,cnd,None)
+ return prd
+
+
+def createStructuralMember(ifcfile,ifcbin,obj):
+
+ """Creates a structural member if possible. Returns the member"""
+
+ global structural_nodes
+ prd = None
+ import Draft
+ import Part
+ import ifcopenshell
+ uid = ifcopenshell.guid.new
+ owh = ifcfile.by_type("IfcOwnerHistory")[0]
+ ctx = getStructuralContext(ifcfile)
+ edges = None
+ if Draft.getType(obj) not in ["Structure"]:
+ if ALLOW_LINEAR_OBJECTS and obj.isDerivedFrom("Part::Feature"):
+ if obj.Shape.Faces:
+ return None
+ elif not obj.Shape.Edges:
+ return None
+ else:
+ edges = obj.Shape.Edges
+ else:
+ wire = Part.makePolygon([obj.Placement.multVec(n) for n in obj.Nodes])
+ edges = wire.Edges
+ if not edges:
+ return None
+ for edge in edges:
+ if len(edge.Vertexes) > 1:
+ # we don't care about curved edges just now...
+ v0 = edge.Vertexes[0].Point.multiply(scaling)
+ v1 = edge.Vertexes[-1].Point.multiply(scaling)
+ cp1 = ifcbin.createIfcCartesianPoint(tuple(v0))
+ cp2 = ifcbin.createIfcCartesianPoint(tuple(v1))
+ upv = ifcbin.createIfcDirection((0,0,1))
+ pla = ifcbin.createIfcLocalPlacement()
+ vp1 = ifcfile.createIfcVertexPoint(cp1)
+ vp2 = ifcfile.createIfcVertexPoint(cp2)
+ edg = ifcfile.createIfcEdge(vp1,vp2)
+ rep = ifcfile.createIfcTopologyRepresentation(ctx,'Analysis','Edge',[edg])
+ psh = ifcfile.createIfcProductDefinitionShape(None,None,[rep])
+ prd = ifcfile.createIfcStructuralCurveMember(uid(),owh,obj.Label,None,None,pla,psh,"RIGID_JOINED_MEMBER",upv)
+ # check for existing connection nodes
+ for v in [v0,v1]:
+ vk = tuple(v)
+ if vk in structural_nodes:
+ if structural_nodes[vk]:
+ n = structural_nodes[vk]
+ else:
+ # there is another member with same point, create a new node
+ n = createStructuralNode(ifcfile,ifcbin,v)
+ structural_nodes[vk] = n
+ ifcfile.createIfcRelConnectsStructuralMember(uid(),None,None,None,prd,n,None,None,None,None);
+ else:
+ # just add the point, no other member using it yet
+ structural_nodes[vk] = None
+ return prd
+
+
+def createStructuralGroup(ifcfile):
+
+ "Assigns all structural objects found in the file to the structual model"""
+
+ import ifcopenshell
+ uid = ifcopenshell.guid.new
+ owh = ifcfile.by_type("IfcOwnerHistory")[0]
+ edges = ifcfile.by_type("IfcStructuralCurveMember")
+ verts = ifcfile.by_type("IfcStructuralPointConnection")
+ model = ifcfile.by_type("IfcStructuralAnalysisModel")[0]
+ if model:
+ members = edges + verts
+ if members:
+ ifcfile.createIfcRelAssignsToGroup(uid(),owh,None,None,members,"PRODUCT",model)
+
+
+def associates(ifcfile,aobj,sobj):
+
+ """Associates an arch object with a struct object"""
+
+ # This is probably not the right way to do this, ie. relate a structural
+ # object with an IfcProduct. Needs to investigate more....
+
+ import ifcopenshell
+ uid = ifcopenshell.guid.new
+ owh = ifcfile.by_type("IfcOwnerHistory")[0]
+ ifcfile.createIfcRelAssignsToProduct(uid(),owh,None,None,[sobj],None,aobj)