freecad-cam/Mod/BIM/importers/exportIFCStructuralTools.py
2026-02-01 01:59:24 +01:00

333 lines
16 KiB
Python

# ***************************************************************************
# * 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 *
# * *
# ***************************************************************************
__title__ = "FreeCAD structural IFC export tools"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.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
structural_curves = {} # this keeps track of structural curves 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
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
project = ifcfile.by_type("IfcProject")[0]
structContext = createStructuralContext(ifcfile)
if ifcfile.wrapped_data.schema_name() == "IFC2X3":
mod = ifcfile.createIfcStructuralAnalysisModel(
uid(), ownerHistory, "Structural Analysis Model", None, None, "NOTDEFINED", None, None, None)
else:
localPlacement = ifcbin.createIfcLocalPlacement()
structModel = ifcfile.createIfcStructuralAnalysisModel(
uid(), ownerHistory, "Structural Analysis Model", None, None, "NOTDEFINED", None, None, None, localPlacement)
relation = ifcfile.createIfcRelDeclares(uid(), ownerHistory, None, None, project, [structModel])
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"]
geomContext = contexts[0] # arbitrarily take the first one...
structContext = ifcfile.createIfcGeometricRepresentationSubContext(
'Analysis', 'Axis', None, None, None, None, geomContext, 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
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
structContext = getStructuralContext(ifcfile)
cartPoint = ifcbin.createIfcCartesianPoint(tuple(point))
vertPoint = ifcfile.createIfcVertexPoint(cartPoint)
topologyRep = ifcfile.createIfcTopologyRepresentation(structContext, 'Analysis', 'Vertex', [vertPoint])
prodDefShape = ifcfile.createIfcProductDefinitionShape(None, None, [topologyRep])
# boundary conditions serve for ex. to create fixed nodes
# appliedCondition = 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
appliedCondition = None
localPlacement = ifcbin.createIfcLocalPlacement()
if ifcfile.wrapped_data.schema_name() == "IFC2X3":
structPntConn = ifcfile.createIfcStructuralPointConnection(
uid(), ownerHistory, 'Vertex', None, None, localPlacement, prodDefShape, appliedCondition)
else:
structPntConn = ifcfile.createIfcStructuralPointConnection(
uid(), ownerHistory, 'Vertex', None, None, localPlacement, prodDefShape, appliedCondition, None)
return structPntConn
def createStructuralCurve(ifcfile, ifcbin, curve):
"""Creates a structural connection for a curve"""
import ifcopenshell
uid = ifcopenshell.guid.new
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
structContext = getStructuralContext(ifcfile)
cartPnt1 = ifcbin.createIfcCartesianPoint(tuple(curve.Vertexes[ 0].Point.multiply(scaling)))
cartPnt2 = ifcbin.createIfcCartesianPoint(tuple(curve.Vertexes[-1].Point.multiply(scaling)))
vertPnt1 = ifcfile.createIfcVertexPoint(cartPnt1)
vertPnt2 = ifcfile.createIfcVertexPoint(cartPnt2)
edge = ifcfile.createIfcEdge(vertPnt1, vertPnt2)
topologyRep = ifcfile.createIfcTopologyRepresentation(structContext, "Analysis", "Edge", [edge])
prodDefShape = ifcfile.createIfcProductDefinitionShape(None, None , [topologyRep])
# boundary conditions serve for ex. to create fixed edges
# for now we don't create any boundary condition
appliedCondition = None
localPlacement = ifcbin.createIfcLocalPlacement()
origin = ifcfile.createIfcCartesianPoint((0.0, 0.0, 0.0))
orientation = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
xAxis = ifcfile.createIfcDirection(tuple(orientation[0]))
zAxis = ifcfile.createIfcDirection(tuple(orientation[2]))
localAxes = ifcfile.createIfcAxis2Placement3D(origin, zAxis, xAxis)
structCrvConn = ifcfile.createIfcStructuralCurveConnection(
uid(), ownerHistory, "Line", None, None, localPlacement, prodDefShape, appliedCondition, localAxes)
return structCrvConn
def createStructuralMember(ifcfile, ifcbin, obj):
"""Creates a structural member if possible. Returns the member"""
global structural_nodes, structural_curves
structuralMember = None
import Draft
import Part
import ifcopenshell
import FreeCAD
uid = ifcopenshell.guid.new
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
structContext = getStructuralContext(ifcfile)
# find edges to convert into structural members
edges = None
if Draft.getType(obj) not in ["Structure"]:
# for non structural elements
if ALLOW_LINEAR_OBJECTS and obj.isDerivedFrom("Part::Feature"):
# for objects created with Part workbench
if obj.Shape.Faces:
# for objects with faces
return None
elif not obj.Shape.Edges:
# for objects without edges
return None
else:
edges = obj.Shape.Edges
else:
# for structural elements with nodes
nodes = [obj.Placement.multVec(n) for n in obj.Nodes]
if len(nodes) > 2:
# when there are more then 2 nodes (i.e. for slabs) append closing node to produce closing edge
nodes.append(nodes[0])
wire = Part.makePolygon(nodes)
edges = wire.Edges
if not edges:
return None
# OBJECT CLASSIFICATION by edge number
# Linear elements for edge_number = 1, Surface elements for edge_number > 1
# we don't care about curved edges just now...
if len(edges) == 1:
# LINEAR OBJECTS: beams, columns
# ATM limitations:
# - no profile properties are taken into account
# - no materials properties are takein into account
# -
# create geometry
verts = [None for _ in range(len(edges)+1)]
verts[0] = tuple(edges[0].Vertexes[ 0].Point.multiply(scaling))
verts[1] = tuple(edges[0].Vertexes[-1].Point.multiply(scaling))
cartPnt1 = ifcfile.createIfcCartesianPoint(verts[0])
cartPnt2 = ifcfile.createIfcCartesianPoint(verts[1])
vertPnt1 = ifcfile.createIfcVertexPoint(cartPnt1)
vertPnt2 = ifcfile.createIfcVertexPoint(cartPnt2)
newEdge = ifcfile.createIfcEdge(vertPnt1, vertPnt2)
topologyRep = ifcfile.createIfcTopologyRepresentation(structContext, "Analysis", "Edge", (newEdge,))
prodDefShape = ifcfile.createIfcProductDefinitionShape(None, None, (topologyRep,))
# set local coordinate system
localPlacement = ifcbin.createIfcLocalPlacement()
localZAxis = ifcbin.createIfcDirection((0, 0, 1))
# create structural member
if ifcfile.wrapped_data.schema_name() == "IFC2X3":
structuralMember = ifcfile.createIfcStructuralCurveMember(
uid(), ownerHistory, obj.Label, None, None, localPlacement, prodDefShape, "RIGID_JOINED_MEMBER")
else:
localZAxis = ifcbin.createIfcDirection((0, 0, 1))
structuralMember = ifcfile.createIfcStructuralCurveMember(
uid(), ownerHistory, obj.Label, None, None, localPlacement, prodDefShape, "RIGID_JOINED_MEMBER", localZAxis)
elif len(edges) > 1:
# SURFACE OBJECTS: slabs (horizontal, vertical, inclined)
# ATM limitations:
# - mo material properties are taken into account
# - walls don't work because they miss a node system
# -
# creates geometry
verts = [None for _ in range(len(edges))]
for i, edge in enumerate(edges):
verts[i] = tuple(edge.Vertexes[0].Point.multiply(scaling))
cartPnt = ifcfile.createIfcCartesianPoint(verts[i])
vertPnt = ifcfile.createIfcVertexPoint(cartPnt)
orientedEdges = [None for _ in range(len(edges))]
for i, vert in enumerate(verts):
v2Index = (i + 1) if i < len(verts) - 1 else 0
cartPnt1 = ifcfile.createIfcCartesianPoint(vert)
cartPnt2 = ifcfile.createIfcCartesianPoint(verts[v2Index])
vertPnt1 = ifcfile.createIfcVertexPoint(cartPnt1)
vertPnt2 = ifcfile.createIfcVertexPoint(cartPnt2)
edge = ifcfile.createIfcEdge(vertPnt1, vertPnt2)
orientedEdges[i] = ifcfile.createIfcOrientedEdge(None, None, edge, True)
edgeLoop = ifcfile.createIfcEdgeLoop(tuple(orientedEdges))
# sets local coordinate system
localPlacement = ifcbin.createIfcLocalPlacement()
# sets face origin to the first vertex point of the planar surface
origin = cartPnt2
# find crossVect that is perpendicular to the planar surface
vect0 = FreeCAD.Vector(verts[0])
vect1 = FreeCAD.Vector(verts[1])
vectn = FreeCAD.Vector(verts[-1])
vect01 = vect1.sub(vect0)
vect0n = vectn.sub(vect0)
crossVect = vect01.cross(vect0n)
# normalize crossVect
normVect = crossVect.normalize()
xAxis = ifcfile.createIfcDirection(tuple([vect01.x, vect01.y, vect01.z]))
zAxis = ifcfile.createIfcDirection(tuple([normVect.x, normVect.y, normVect.z]))
localAxes = ifcfile.createIfcAxis2Placement3D(origin, zAxis, xAxis)
plane = ifcfile.createIfcPlane(localAxes)
faceBound = ifcfile.createIfcFaceBound(edgeLoop, True)
face = ifcfile.createIfcFaceSurface((faceBound,), plane, True)
topologyRep = ifcfile.createIfcTopologyRepresentation(structContext, "Analysis", "Face", (face,))
prodDefShape = ifcfile.createIfcProductDefinitionShape(None, None, (topologyRep,))
# sets surface thickness
# TODO: ATM limitations
# - for vertical slabs (walls) or inclined slabs (ramps) the thickness is taken from the Height property
thickness = float(obj.Height)*scaling
# creates structural member
structuralMember = ifcfile.createIfcStructuralSurfaceMember(
uid(), ownerHistory, obj.Label, None, None, localPlacement, prodDefShape, "SHELL", thickness)
# check for existing connection nodes
for vert in verts:
vertCoord = tuple(vert)
if vertCoord in structural_nodes:
if structural_nodes[vertCoord]:
# there is already another member using this point
structPntConn = structural_nodes[vertCoord]
else:
# there is another member with same point, create a new node connection
structPntConn = createStructuralNode(ifcfile, ifcbin, vert)
structural_nodes[vertCoord] = structPntConn
ifcfile.createIfcRelConnectsStructuralMember(
uid(), ownerHistory, None, None, structuralMember, structPntConn, None, None, None, None)
else:
# just add the point, no other member using it yet
structural_nodes[vertCoord] = None
# check for existing connection curves
for edge in edges:
verts12 = tuple([edge.Vertexes[ 0].Point.x, edge.Vertexes[ 0].Point.y, edge.Vertexes[ 0].Point.z,
edge.Vertexes[-1].Point.x, edge.Vertexes[-1].Point.y, edge.Vertexes[-1].Point.z])
verts21 = tuple([edge.Vertexes[-1].Point.x, edge.Vertexes[-1].Point.y, edge.Vertexes[-1].Point.z,
edge.Vertexes[ 0].Point.x, edge.Vertexes[ 0].Point.y, edge.Vertexes[ 0].Point.z])
verts12_in_curves = verts12 in structural_curves
verts21_in_curves = verts21 in structural_curves
if verts21_in_curves:
verts = verts21
else:
verts = verts12
if (verts12_in_curves or verts21_in_curves):
if structural_curves[verts]:
# there is already another member using this curve
strucCrvConn = structural_curves[verts]
else:
# there is another member with same edge, create a new curve connection
strucCrvConn = createStructuralCurve(ifcfile, ifcbin, edge)
structural_curves[verts] = strucCrvConn
ifcfile.createIfcRelConnectsStructuralMember(
uid(), None, None, None, structuralMember, strucCrvConn, None, None, None, None)
else:
# just add the curve, no other member using it yet
structural_curves[verts] = None
return structuralMember
def createStructuralGroup(ifcfile):
"""Assigns all structural objects found in the file to the structural model"""
import ifcopenshell
uid = ifcopenshell.guid.new
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
structSrfMember = ifcfile.by_type("IfcStructuralSurfaceMember")
structCrvMember = ifcfile.by_type("IfcStructuralCurveMember")
structPntConn = ifcfile.by_type("IfcStructuralPointConnection")
structCrvConn = ifcfile.by_type("IfcStructuralCurveConnection")
structModel = ifcfile.by_type("IfcStructuralAnalysisModel")[0]
if structModel:
members = structSrfMember + structCrvMember + structPntConn + structCrvConn
if members:
ifcfile.createIfcRelAssignsToGroup(uid(), ownerHistory, None, None, members, "PRODUCT", structModel)
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
ownerHistory = ifcfile.by_type("IfcOwnerHistory")[0]
ifcfile.createIfcRelAssignsToProduct(uid(), ownerHistory, None, None, [sobj], None, aobj)