Skip to content

Commit

Permalink
Arch: Allow to export structural analysis model to IFC
Browse files Browse the repository at this point in the history
  • Loading branch information
yorikvanhavre committed Jun 11, 2020
1 parent 415b0dc commit 6271887
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/Mod/Arch/CMakeLists.txt
Expand Up @@ -54,6 +54,7 @@ SET(Arch_SRCS
ArchTruss.py
ArchCurtainWall.py
importSHP.py
exportIFCStructuralTools.py
)

SET(Dice3DS_SRCS
Expand Down
39 changes: 39 additions & 0 deletions src/Mod/Arch/Resources/ui/preferences-ifc-export.ui
Expand Up @@ -39,6 +39,45 @@
<string>Export options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Export type</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefComboBox" name="comboBox">
<property name="toolTip">
<string>The type of objects you wish to export: Standard (solid objects), wireframe model for structural analysis, or both in a same model</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcExportModel</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
<item>
<property name="text">
<string>Standard model</string>
</property>
</item>
<item>
<property name="text">
<string>Structural analysis</string>
</property>
</item>
<item>
<property name="text">
<string>Standard + structural</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox">
<property name="toolTip">
Expand Down
56 changes: 35 additions & 21 deletions src/Mod/Arch/exportIFC.py
Expand Up @@ -38,6 +38,7 @@
import DraftVecUtils
import ArchIFCSchema
import exportIFCHelper
import exportIFCStructuralTools

from DraftGeomUtils import vec
from importIFCHelper import dd2dms
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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, ... }
Expand All @@ -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']:
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))))
Expand Down Expand Up @@ -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])

Expand Down
10 changes: 8 additions & 2 deletions src/Mod/Arch/exportIFCHelper.py
Expand Up @@ -280,7 +280,11 @@ def createIfcPropertySingleValue(self,name,ptype,pvalue):
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:
Expand Down Expand Up @@ -310,7 +314,9 @@ def createIfcAxis2Placement2D(self,p1,p2):
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
Expand Down
180 changes: 180 additions & 0 deletions src/Mod/Arch/exportIFCStructuralTools.py
@@ -0,0 +1,180 @@
# ***************************************************************************
# * Copyright (c) 2020 Yorik van Havre <yorik@uncreated.net> *
# * *
# * 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)

0 comments on commit 6271887

Please sign in to comment.