BIM: NativeIFC 2D support - basic import/export + linework annotations

This commit is contained in:
Yorik van Havre
2024-09-18 10:49:22 +02:00
committed by Yorik van Havre
parent 7913280486
commit 9c9d451ac6
6 changed files with 356 additions and 210 deletions

View File

@@ -189,6 +189,7 @@ SET(nativeifc_SRCS
nativeifc/__init__.py
nativeifc/ifc_openshell.py
nativeifc/ifc_types.py
nativeifc/ifc_export.py
)
SOURCE_GROUP("" FILES ${Arch_SRCS})

View File

@@ -1289,125 +1289,11 @@ def export(exportList, filename, colors=None, preferences=None):
annos = {}
if preferences['EXPORT_2D']:
global curvestyles
curvestyles = {}
if annotations and preferences['DEBUG']: print("exporting 2D objects...")
for anno in annotations:
objectType = 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)
if anno.isDerivedFrom("Part::Feature"):
if Draft.getType(anno) == "Hatch":
objectType = "HATCH"
elif getattr(anno.ViewObject,"EndArrow",False):
objectType = "LEADER"
elif anno.Shape.Faces:
objectType = "AREA"
else:
objectType = "LINEWORK"
reps = []
sh = anno.Shape.copy()
sh.scale(preferences['SCALE_FACTOR']) # to meters
ehc = []
curves = []
for w in sh.Wires:
curves.append(createCurve(ifcfile,w))
for e in w.Edges:
ehc.append(e.hashCode())
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
curves = []
for e in sh.Edges:
if e.hashCode not in ehc:
curves.append(createCurve(ifcfile,e))
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
elif anno.isDerivedFrom("App::Annotation"):
objectType = "TEXT"
l = FreeCAD.Vector(anno.Position).multiply(preferences['SCALE_FACTOR'])
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None)
s = ";".join(anno.LabelText)
txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT")
reps = [txt]
elif Draft.getType(anno) in ["DraftText","Text"]:
objectType = "TEXT"
l = FreeCAD.Vector(anno.Placement.Base).multiply(preferences['SCALE_FACTOR'])
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
zdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))))
xdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0))))
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
alg = "LEFT"
if FreeCAD.GuiUp and hasattr(anno.ViewObject,"Justification"):
if anno.ViewObject.Justification == "Right":
alg = "RIGHT"
s = ";".join(anno.Text)
txt = ifcfile.createIfcTextLiteral(s,tpl,alg)
reps = [txt]
elif Draft.getType(anno) in ["Dimension","LinearDimension","AngularDimension"]:
if FreeCAD.GuiUp:
objectType = "DIMENSION"
vp = anno.ViewObject.Proxy
reps = []
sh = Part.makePolygon([vp.p1,vp.p2,vp.p3,vp.p4])
sh.scale(preferences['SCALE_FACTOR']) # to meters
ehc = []
curves = []
for w in sh.Wires:
curves.append(createCurve(ifcfile,w))
for e in w.Edges:
ehc.append(e.hashCode())
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
curves = []
for e in sh.Edges:
if e.hashCode not in ehc:
curves.append(createCurve(ifcfile,e))
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
l = FreeCAD.Vector(vp.tbase).multiply(preferences['SCALE_FACTOR'])
zdir = None
xdir = None
if hasattr(vp,"trot"):
r = FreeCAD.Rotation(vp.trot[0],vp.trot[1],vp.trot[2],vp.trot[3])
zdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(0,0,1))))
xdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(1,0,0))))
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
txt = ifcfile.createIfcTextLiteral(vp.string,tpl,"LEFT")
reps.append(txt)
else:
print("Unable to handle object",anno.Label)
continue
for coldef in ["LineColor","TextColor","ShapeColor"]:
if hasattr(obj.ViewObject,coldef):
rgb = getattr(obj.ViewObject,coldef)[:3]
if rgb in curvestyles:
psa = curvestyles[rgb]
else:
col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2])
cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("continuous")
ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col)
psa = ifcfile.createIfcPresentationStyleAssignment([ics])
curvestyles[rgb] = psa
for rep in reps:
isi = ifcfile.createIfcStyledItem(rep,[psa],None)
break
shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps)
rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp])
l = anno.Label
ann = ifcfile.createIfcAnnotation(
ifcopenshell.guid.new(),
history,l,
'',
objectType,
placement,
rep
)
ann = create_annotation(anno, ifcfile, context, history, preferences)
annos[anno.Name] = ann
# groups
@@ -2550,3 +2436,128 @@ def writeJson(filename,ifcfile):
#print("json:",s)
f.write(s)
f.close()
def create_annotation(anno, ifcfile, context, history, preferences):
"""Creates an annotation object"""
# uses global ifcbin, curvestyles
objectType = 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)
if anno.isDerivedFrom("Part::Feature"):
if Draft.getType(anno) == "Hatch":
objectType = "HATCH"
elif getattr(anno.ViewObject,"EndArrow",False):
objectType = "LEADER"
elif anno.Shape.Faces:
objectType = "AREA"
else:
objectType = "LINEWORK"
reps = []
sh = anno.Shape.copy()
sh.scale(preferences['SCALE_FACTOR']) # to meters
ehc = []
curves = []
for w in sh.Wires:
curves.append(createCurve(ifcfile,w))
for e in w.Edges:
ehc.append(e.hashCode())
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
curves = []
for e in sh.Edges:
if e.hashCode not in ehc:
curves.append(createCurve(ifcfile,e))
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
elif anno.isDerivedFrom("App::Annotation"):
objectType = "TEXT"
l = FreeCAD.Vector(anno.Position).multiply(preferences['SCALE_FACTOR'])
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None)
s = ";".join(anno.LabelText)
txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT")
reps = [txt]
elif Draft.getType(anno) in ["DraftText","Text"]:
objectType = "TEXT"
l = FreeCAD.Vector(anno.Placement.Base).multiply(preferences['SCALE_FACTOR'])
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
zdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))))
xdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0))))
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
alg = "LEFT"
if FreeCAD.GuiUp and hasattr(anno.ViewObject,"Justification"):
if anno.ViewObject.Justification == "Right":
alg = "RIGHT"
s = ";".join(anno.Text)
txt = ifcfile.createIfcTextLiteral(s,tpl,alg)
reps = [txt]
elif Draft.getType(anno) in ["Dimension","LinearDimension","AngularDimension"]:
if FreeCAD.GuiUp:
objectType = "DIMENSION"
vp = anno.ViewObject.Proxy
reps = []
sh = Part.makePolygon([vp.p1,vp.p2,vp.p3,vp.p4])
sh.scale(preferences['SCALE_FACTOR']) # to meters
ehc = []
curves = []
for w in sh.Wires:
curves.append(createCurve(ifcfile,w))
for e in w.Edges:
ehc.append(e.hashCode())
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
curves = []
for e in sh.Edges:
if e.hashCode not in ehc:
curves.append(createCurve(ifcfile,e))
if curves:
reps.append(ifcfile.createIfcGeometricCurveSet(curves))
l = FreeCAD.Vector(vp.tbase).multiply(preferences['SCALE_FACTOR'])
zdir = None
xdir = None
if hasattr(vp,"trot"):
r = FreeCAD.Rotation(vp.trot[0],vp.trot[1],vp.trot[2],vp.trot[3])
zdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(0,0,1))))
xdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(1,0,0))))
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
txt = ifcfile.createIfcTextLiteral(vp.string,tpl,"LEFT")
reps.append(txt)
else:
print("Unable to handle object",anno.Label)
return None
for coldef in ["LineColor","TextColor","ShapeColor"]:
if hasattr(anno.ViewObject,coldef):
rgb = getattr(anno.ViewObject,coldef)[:3]
if rgb in curvestyles:
psa = curvestyles[rgb]
else:
col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2])
cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("continuous")
ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col)
psa = ifcfile.createIfcPresentationStyleAssignment([ics])
curvestyles[rgb] = psa
for rep in reps:
isi = ifcfile.createIfcStyledItem(rep,[psa],None)
break
shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps)
rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp])
label = anno.Label
description = getattr(anno, "Description", "")
ann = ifcfile.createIfcAnnotation(
ifcopenshell.guid.new(),
history,
label,
description,
objectType,
placement,
rep
)
return ann

View File

@@ -0,0 +1,146 @@
# ***************************************************************************
# * *
# * Copyright (c) 2024 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 General Public License (GPL) *
# * as published by the Free Software Foundation; either version 3 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 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 *
# * *
# ***************************************************************************
import FreeCAD
import Draft
import ifcopenshell
from importers import exportIFC
from importers import exportIFCHelper
from nativeifc import ifc_tools
def get_export_preferences(ifcfile):
"""returns a preferences dict for exportIFC"""
prefs = exportIFC.getPreferences()
prefs["SCHEMA"] = ifcfile.wrapped_data.schema_name()
s = ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
# the above lines yields meter -> file unit scale factor. We need mm
prefs["SCALE_FACTOR"] = 0.001 / s
context = ifcfile[
ifc_tools.get_body_context_ids(ifcfile)[0]
] # we take the first one (first found subcontext)
return prefs, context
def create_product(obj, parent, ifcfile, ifcclass=None):
"""Creates an IFC product out of a FreeCAD object"""
name = obj.Label
description = getattr(obj, "Description", None)
if not ifcclass:
ifcclass = ifc_tools.get_ifctype(obj)
representation, placement = create_representation(obj, ifcfile)
product = ifc_tools.api_run("root.create_entity", ifcfile, ifc_class=ifcclass, name=name)
ifc_tools.set_attribute(ifcfile, product, "Description", description)
ifc_tools.set_attribute(ifcfile, product, "ObjectPlacement", placement)
# TODO below cannot be used at the moment because the ArchIFC exporter returns an
# IfcProductDefinitionShape already and not an IfcShapeRepresentation
# ifc_tools.api_run("geometry.assign_representation", ifcfile, product=product, representation=representation)
ifc_tools.set_attribute(ifcfile, product, "Representation", representation)
# TODO treat subtractions/additions
return product
def create_representation(obj, ifcfile):
"""Creates a geometry representation for the given object"""
# TEMPORARY use the Arch exporter
# TODO this is temporary. We should rely on ifcopenshell for this with:
# https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/root/create_entity/index.html
# a new FreeCAD 'engine' should be added to:
# https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/geometry/index.html
# that should contain all typical use cases one could have to convert FreeCAD geometry
# to IFC.
# setup exporter - TODO do that in the module init
exportIFC.clones = {}
exportIFC.profiledefs = {}
exportIFC.surfstyles = {}
exportIFC.shapedefs = {}
exportIFC.ifcopenshell = ifcopenshell
exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False)
prefs, context = get_export_preferences(ifcfile)
representation, placement, shapetype = exportIFC.getRepresentation(
ifcfile, context, obj, preferences=prefs
)
return representation, placement
def is_annotation(obj):
"""Determines if the given FreeCAD object should be saved as an IfcAnnotation"""
if getattr(obj, "IfcClass", None) == "IfcAnnotation":
return True
if getattr(obj, "IfcType", None) == "Annotation":
return True
if obj.isDerivedFrom("Part::Part2DObject"):
return True
elif obj.isDerivedFrom("App::Annotation"):
return True
elif Draft.getType(obj) in ["DraftText",
"Text",
"Dimension",
"LinearDimension",
"AngularDimension"]:
return True
elif obj.isDerivedFrom("Part::Feature"):
if obj.Shape and (not obj.Shape.Solids) and obj.Shape.Edges:
if not obj.Shape.Faces:
return True
elif (obj.Shape.BoundBox.XLength < 0.0001) \
or (obj.Shape.BoundBox.YLength < 0.0001) \
or (obj.Shape.BoundBox.ZLength < 0.0001):
return True
return False
def create_annotation(obj, ifcfile):
"""Adds an IfcAnnotation from the given object to the given IFC file"""
exportIFC.clones = {}
exportIFC.profiledefs = {}
exportIFC.surfstyles = {}
exportIFC.shapedefs = {}
exportIFC.curvestyles = {}
exportIFC.ifcopenshell = ifcopenshell
exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False)
prefs, context = get_export_preferences(ifcfile)
history = get_history(ifcfile)
# TODO The following prints each edge as a separate IfcGeometricCurveSet
# It should be refined to create polylines instead
anno = exportIFC.create_annotation(obj, ifcfile, context, history, prefs)
return anno
def get_history(ifcfile):
"""Returns the owner history or None"""
history = ifcfile.by_type("IfcOwnerHistory")
if history:
history = history[0]
else:
# IFC4 allows to not write any history
history = None
return history

View File

@@ -26,12 +26,14 @@ used by the execute() method of ifc_objects"""
import time
import re
import FreeCAD
from FreeCAD import Base
import Part
import ifcopenshell
from ifcopenshell.util import element
from nativeifc import ifc_tools
from nativeifc import ifc_export
import multiprocessing
import FreeCADGui
from pivy import coin
@@ -57,13 +59,32 @@ def generate_geometry(obj, cached=False):
return
colors = None
# workaround for Group property bug: Avoid having a null shape, otherwise
# a default representation will be created from the object's Group contents
# obj.Shape = Part.makeBox(1, 1, 1)
# fixed in FreeCAD 0.22 - uncomment the line above for earlier versions
ifcfile = ifc_tools.get_ifcfile(obj)
# annotations
if ifc_export.is_annotation(obj):
element = ifc_tools.get_ifc_element(obj)
if not element:
return
if obj.ShapeMode == "Shape":
shape, placement = get_annotation_shape(element, ifcfile)
if shape:
obj.Shape = shape
if placement:
obj.Placement = placement
elif obj.ViewObject and obj.ShapeMode == "Coin":
node, placement = get_annotation_shape(element, ifcfile, coin=True)
if node:
set_representation(obj.ViewObject, node)
colors = node[0]
else:
set_representation(obj.ViewObject, None)
print_debug(obj)
if placement:
obj.Placement = placement
return
# generate the shape or coin node
ifcfile = ifc_tools.get_ifcfile(obj)
elements = get_decomposition(obj)
if obj.ShapeMode == "Shape":
shape, colors = generate_shape(ifcfile, elements, cached)
@@ -77,6 +98,7 @@ def generate_geometry(obj, cached=False):
elif obj.ViewObject and obj.ShapeMode == "Coin":
node, placement = generate_coin(ifcfile, elements, cached)
if node:
# TODO this still needs to be fixed
#QtCore.QTimer.singleShot(0, lambda: set_representation(obj.ViewObject, node))
set_representation(obj.ViewObject, node)
colors = node[0]
@@ -339,7 +361,6 @@ def filter_types(elements, obj_ids=[]):
elements = [e for e in elements if not e.is_a("IfcOpeningElement")]
elements = [e for e in elements if not e.is_a("IfcSpace")]
elements = [e for e in elements if not e.is_a("IfcFurnishingElement")]
elements = [e for e in elements if not e.is_a("IfcAnnotation")]
elements = [e for e in elements if not e.id() in obj_ids]
return elements
@@ -451,11 +472,12 @@ def set_representation(vobj, node):
coords.point.deleteValues(0)
if not node:
return
if node[1] and node[2] and node[3] and node[4]:
if node[1] and node[3]:
coords.point.setValues(node[1])
fset.coordIndex.setValues(node[2])
fset.partIndex.setValues(node[4])
eset.coordIndex.setValues(node[3])
if node[2] and node[4]:
fset.coordIndex.setValues(node[2])
fset.partIndex.setValues(node[4])
def print_debug(obj):
@@ -524,3 +546,45 @@ def delete_ghost(document):
sg = FreeCADGui.getDocument(document.Name).ActiveView.getSceneGraph()
sg.removeChild(document.Proxy.ghost)
del document.Proxy.ghost
def get_annotation_shape(annotation, ifcfile, coin=False):
"""Returns a shape or a coin node form an IFC annotation"""
import Part
from importers import importIFCHelper
shape = None
placement = None
ifcscale = importIFCHelper.getScaling(ifcfile)
shapes2d = []
for rep in annotation.Representation.Representations:
if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]:
sh = importIFCHelper.get2DShape(rep, ifcscale)
if sh:
shapes2d.extend(sh)
if shapes2d:
shape = Part.makeCompound(shapes2d)
placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale)
if coin:
iv = shape.writeInventor()
iv = iv.replace("\n", "")
segs = re.findall(r"point \[.*?\]",iv)
segs = [s.replace("point [","").replace("]","").strip() for s in segs]
segs = [s.split(" ") for s in segs]
verts = []
edges = []
for pair in segs:
v1 = tuple([float(v) for v in pair[0].split()])
v2 = tuple([float(v) for v in pair[1].split()])
if not v1 in verts:
verts.append(v1)
edges.append(verts.index(v1))
if not v2 in verts:
verts.append(v2)
edges.append(verts.index(v2))
edges.append(-1)
shape = [[None, verts, [], edges]]
# unify nodes
shape = unify(shape)
return shape, placement

View File

@@ -355,6 +355,7 @@ def lock_document():
from nativeifc import ifc_tools # lazy loading
from importers import exportIFC
from nativeifc import ifc_geometry
from nativeifc import ifc_export
from PySide import QtCore
doc = FreeCAD.ActiveDocument
@@ -379,7 +380,7 @@ def lock_document():
if rest:
# 1b some objects are outside
objs = find_toplevel(rest)
prefs, context = ifc_tools.get_export_preferences(ifcfile)
prefs, context = ifc_export.get_export_preferences(ifcfile)
products = exportIFC.export(objs, ifcfile, preferences=prefs)
for product in products.values():
if not getattr(product, "ContainedInStructure", None):
@@ -416,7 +417,7 @@ def lock_document():
ifc_tools.convert_document(doc, silent=True)
ifcfile = doc.Proxy.ifcfile
objs = find_toplevel(doc.Objects)
prefs, context = ifc_tools.get_export_preferences(ifcfile)
prefs, context = ifc_export.get_export_preferences(ifcfile)
exportIFC.export(objs, ifcfile, preferences=prefs)
for n in [o.Name for o in doc.Objects]:
if doc.getObject(n):

View File

@@ -29,8 +29,6 @@ import os
import FreeCAD
import Draft
import Arch
from importers import exportIFC
from importers import exportIFCHelper
import ifcopenshell
from ifcopenshell import geom
@@ -47,6 +45,7 @@ from nativeifc import ifc_viewproviders
from nativeifc import ifc_import
from nativeifc import ifc_layers
from nativeifc import ifc_status
from nativeifc import ifc_export
SCALE = 1000.0 # IfcOpenShell works in meters, FreeCAD works in mm
SHORT = False # If True, only Step ID attribute is created
@@ -739,8 +738,6 @@ def filter_elements(elements, ifcfile, expand=True, spaces=False, assemblies=Tru
elements = [e for e in elements if not e.is_a("IfcProject")]
# skip furniture for now, they can be lazy loaded probably
elements = [e for e in elements if not e.is_a("IfcFurnishingElement")]
# skip annotations for now
elements = [e for e in elements if not e.is_a("IfcAnnotation")]
return elements
@@ -1011,7 +1008,10 @@ def aggregate(obj, parent, mode=None):
ifcclass = None
if mode == "opening":
ifcclass = "IfcOpeningElement"
product = create_product(obj, parent, ifcfile, ifcclass)
if ifc_export.is_annotation(obj):
product = ifc_export.create_annotation(obj, ifcfile)
else:
product = ifc_export.create_product(obj, parent, ifcfile, ifcclass)
shapemode = getattr(parent, "ShapeMode", DEFAULT_SHAPEMODE)
newobj = create_object(product, obj.Document, ifcfile, shapemode)
new = True
@@ -1065,69 +1065,6 @@ def deaggregate(obj, parent):
parent.Proxy.removeObject(parent, obj)
def create_product(obj, parent, ifcfile, ifcclass=None):
"""Creates an IFC product out of a FreeCAD object"""
name = obj.Label
description = getattr(obj, "Description", None)
if not ifcclass:
ifcclass = get_ifctype(obj)
representation, placement, shapetype = create_representation(obj, ifcfile)
product = api_run("root.create_entity", ifcfile, ifc_class=ifcclass, name=name)
set_attribute(ifcfile, product, "Description", description)
set_attribute(ifcfile, product, "ObjectPlacement", placement)
# TODO below cannot be used at the moment because the ArchIFC exporter returns an
# IfcProductDefinitionShape already and not an IfcShapeRepresentation
# api_run("geometry.assign_representation", ifcfile, product=product, representation=representation)
set_attribute(ifcfile, product, "Representation", representation)
# additions
if hasattr(obj,"Additions") and shapetype in ["extrusion","no shape"]:
for addobj in obj.Additions:
r2,p2,c2 = create_representation(addobj, ifcfile)
cl2 = get_ifctype(addobj)
addprod = api_run("root.create_entity", ifcfile, ifc_class=cl2, name=addobj.Label)
set_attribute(ifcfile, addprod, "Description", getattr(addobj, "Description", ""))
set_attribute(ifcfile, addprod, "ObjectPlacement", p2)
set_attribute(ifcfile, addprod, "Representation", r2)
create_relationship(None, addobj, product, addprod, ifcfile)
# subtractions
if hasattr(obj,"Subtractions") and shapetype in ["extrusion","no shape"]:
for subobj in obj.Subtractions:
r3,p3,c3 = create_representation(subobj, ifcfile)
cl3 = "IfcOpeningElement"
subprod = api_run("root.create_entity", ifcfile, ifc_class=cl3, name=subobj.Label)
set_attribute(ifcfile, subprod, "Description", getattr(subobj, "Description", ""))
set_attribute(ifcfile, subprod, "ObjectPlacement", p3)
set_attribute(ifcfile, subprod, "Representation", r3)
create_relationship(None, subobj, product, subprod, ifcfile)
return product
def create_representation(obj, ifcfile):
"""Creates a geometry representation for the given object"""
# TEMPORARY use the Arch exporter
# TODO this is temporary. We should rely on ifcopenshell for this with:
# https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/root/create_entity/index.html
# a new FreeCAD 'engine' should be added to:
# https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/geometry/index.html
# that should contain all typical use cases one could have to convert FreeCAD geometry
# to IFC.
# setup exporter - TODO do that in the module init
exportIFC.clones = {}
exportIFC.profiledefs = {}
exportIFC.surfstyles = {}
exportIFC.shapedefs = {}
exportIFC.ifcopenshell = ifcopenshell
exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False)
prefs, context = get_export_preferences(ifcfile)
representation, placement, shapetype = exportIFC.getRepresentation(
ifcfile, context, obj, preferences=prefs
)
return representation, placement, shapetype
def get_ifctype(obj):
"""Returns a valid IFC type from an object"""
@@ -1144,20 +1081,6 @@ def get_ifctype(obj):
return "IfcBuildingElementProxy"
def get_export_preferences(ifcfile):
"""returns a preferences dict for exportIFC"""
prefs = exportIFC.getPreferences()
prefs["SCHEMA"] = ifcfile.wrapped_data.schema_name()
s = ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
# the above lines yields meter -> file unit scale factor. We need mm
prefs["SCALE_FACTOR"] = 0.001 / s
context = ifcfile[
get_body_context_ids(ifcfile)[0]
] # we take the first one (first found subcontext)
return prefs, context
def get_subvolume(obj):
"""returns a subface + subvolume from a window object"""
@@ -1240,7 +1163,7 @@ def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None):
if old_obj:
tempface, tempobj = get_subvolume(old_obj)
if tempobj:
opening = create_product(tempobj, parent, ifcfile, "IfcOpeningElement")
opening = ifc_export.create_product(tempobj, parent, ifcfile, "IfcOpeningElement")
set_attribute(ifcfile, product, "Name", "Opening")
old_obj.Document.removeObject(tempobj.Name)
if tempface: