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)