# *************************************************************************** # * 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 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)