Arch: small cleanup of IFC importer and exporter
Clarify the import of IfcOpenShell's `geom`. In some cases it fails importing and initializing the regular way, `import ifcopenshell.geom`. But it works in this way ``` from ifcopenshell import geom geom.settings() ```
This commit is contained in:
@@ -18,13 +18,18 @@
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
"""Provide the exporter for IFC files used above all in Arch and BIM.
|
||||
|
||||
Internally it uses IfcOpenShell, which must be installed before using.
|
||||
"""
|
||||
## @package exportIFC
|
||||
# \ingroup ARCH
|
||||
# \brief IFC file format exporter
|
||||
#
|
||||
# This module provides tools to export IFC files.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
__title__ = "FreeCAD IFC export"
|
||||
__author__ = "Yorik van Havre","Jonathan Wiedemann","Bernd Hahnebach"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
|
||||
import six
|
||||
import os
|
||||
import time
|
||||
@@ -43,40 +48,38 @@ import exportIFCStructuralTools
|
||||
from DraftGeomUtils import vec
|
||||
from importIFCHelper import dd2dms
|
||||
from importIFCHelper import decode
|
||||
from draftutils.messages import _msg, _err
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
## @package exportIFC
|
||||
# \ingroup ARCH
|
||||
# \brief IFC file format exporter
|
||||
#
|
||||
# This module provides tools to export IFC files.
|
||||
__title__ = "FreeCAD IFC export"
|
||||
__author__ = ("Yorik van Havre", "Jonathan Wiedemann", "Bernd Hahnebach")
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
|
||||
if open.__module__ in ['__builtin__','io']:
|
||||
# Save the Python open function because it will be redefined
|
||||
if open.__module__ in ['__builtin__', 'io']:
|
||||
pyopen = open
|
||||
# pyopen is used in exporter to open a file in Arch
|
||||
|
||||
|
||||
# ************************************************************************************************
|
||||
# ********** templates and other definitions ****
|
||||
|
||||
# specific FreeCAD <-> IFC slang translations
|
||||
# Templates and other definitions ****
|
||||
# Specific FreeCAD <-> IFC slang translations
|
||||
translationtable = {
|
||||
"Foundation":"Footing",
|
||||
"Floor":"BuildingStorey",
|
||||
"Rebar":"ReinforcingBar",
|
||||
"HydroEquipment":"SanitaryTerminal",
|
||||
"ElectricEquipment":"ElectricAppliance",
|
||||
"Furniture":"FurnishingElement",
|
||||
"Stair Flight":"StairFlight",
|
||||
"Curtain Wall":"CurtainWall",
|
||||
"Pipe Segment":"PipeSegment",
|
||||
"Pipe Fitting":"PipeFitting",
|
||||
"VisGroup":"Group",
|
||||
"Undefined":"BuildingElementProxy",
|
||||
}
|
||||
"Foundation": "Footing",
|
||||
"Floor": "BuildingStorey",
|
||||
"Rebar": "ReinforcingBar",
|
||||
"HydroEquipment": "SanitaryTerminal",
|
||||
"ElectricEquipment": "ElectricAppliance",
|
||||
"Furniture": "FurnishingElement",
|
||||
"Stair Flight": "StairFlight",
|
||||
"Curtain Wall": "CurtainWall",
|
||||
"Pipe Segment": "PipeSegment",
|
||||
"Pipe Fitting": "PipeFitting",
|
||||
"VisGroup": "Group",
|
||||
"Undefined": "BuildingElementProxy",
|
||||
}
|
||||
|
||||
|
||||
# the base IFC template for export
|
||||
# The base IFC template for export, the $variables will be substituted
|
||||
# by specific information
|
||||
ifctemplate = """ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
@@ -106,81 +109,99 @@ END-ISO-10303-21;
|
||||
"""
|
||||
|
||||
|
||||
# ************************************************************************************************
|
||||
# ********** get the prefs, available in import and export ****************
|
||||
def getPreferences():
|
||||
|
||||
"""retrieves IFC preferences"""
|
||||
|
||||
"""Retrieve the IFC preferences available in import and export."""
|
||||
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
||||
|
||||
if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False):
|
||||
import FreeCADGui
|
||||
FreeCADGui.showPreferences("Import-Export",1)
|
||||
ifcunit = p.GetInt("ifcUnit",0)
|
||||
if FreeCAD.GuiUp and p.GetBool("ifcShowDialog", False):
|
||||
FreeCADGui.showPreferences("Import-Export", 1)
|
||||
|
||||
ifcunit = p.GetInt("ifcUnit", 0)
|
||||
|
||||
# Factor to multiply the dimension in millimeters
|
||||
# mm x 0.001 = metre
|
||||
# mm x 0.00328084 = foot
|
||||
# mm x 0.03937008 = inch
|
||||
|
||||
# The only real use of these units is to make Revit choose which mode
|
||||
# to work with.
|
||||
#
|
||||
# Inch is not yet implemented, and I don't even know if it is actually
|
||||
# desired
|
||||
|
||||
f = 0.001
|
||||
u = "metre"
|
||||
|
||||
if ifcunit == 1:
|
||||
f = 0.00328084
|
||||
u = "foot"
|
||||
#if ifcunit == "inch":
|
||||
# f = 0.03937008
|
||||
# not yet implemented, and I don't even know if it is interesting to do it.
|
||||
# the only real use of these units is to make revit choose which mode to work with
|
||||
|
||||
# if ifcunit == 2:
|
||||
# f = 0.03937008
|
||||
# u = "inch"
|
||||
|
||||
# Be careful with setting ADD_DEFAULT_SITE, ADD_DEFAULT_BUILDING,
|
||||
# and ADD_DEFAULT_STOREY to False. If this is done the spatial structure
|
||||
# may no longer be fully connected to the `IfcProject`. This means
|
||||
# some objects may be "unreferenced" and won't belong to the `IfcProject`.
|
||||
# Some applications may fail at importing these unreferenced objects.
|
||||
preferences = {
|
||||
'DEBUG': p.GetBool("ifcDebug",False),
|
||||
'CREATE_CLONES': p.GetBool("ifcCreateClones",True),
|
||||
'FORCE_BREP': p.GetBool("ifcExportAsBrep",False),
|
||||
'STORE_UID': p.GetBool("ifcStoreUid",True),
|
||||
'SERIALIZE': p.GetBool("ifcSerialize",False),
|
||||
'EXPORT_2D': p.GetBool("ifcExport2D",True),
|
||||
'FULL_PARAMETRIC': p.GetBool("IfcExportFreeCADProperties",False),
|
||||
'ADD_DEFAULT_SITE': p.GetBool("IfcAddDefaultSite",True),
|
||||
'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True),
|
||||
'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",True),
|
||||
# Be careful with turning one of the three above off.
|
||||
# The spatial structure may no longer be fully connected to the IfcProject.
|
||||
# This would result in unreferenced objects not belonging to the IfcProject.
|
||||
# Some applications do not import unreferenced objects.
|
||||
'DEBUG': p.GetBool("ifcDebug", False),
|
||||
'CREATE_CLONES': p.GetBool("ifcCreateClones", True),
|
||||
'FORCE_BREP': p.GetBool("ifcExportAsBrep", False),
|
||||
'STORE_UID': p.GetBool("ifcStoreUid", True),
|
||||
'SERIALIZE': p.GetBool("ifcSerialize", False),
|
||||
'EXPORT_2D': p.GetBool("ifcExport2D", True),
|
||||
'FULL_PARAMETRIC': p.GetBool("IfcExportFreeCADProperties", False),
|
||||
'ADD_DEFAULT_SITE': p.GetBool("IfcAddDefaultSite", True),
|
||||
'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding", True),
|
||||
'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey", True),
|
||||
'IFC_UNIT': u,
|
||||
'SCALE_FACTOR': f,
|
||||
'GET_STANDARD': p.GetBool("getStandardType",False),
|
||||
'EXPORT_MODEL': ['arch','struct','hybrid'][p.GetInt("ifcExportModel",0)]
|
||||
'GET_STANDARD': p.GetBool("getStandardType", False),
|
||||
'EXPORT_MODEL': ['arch', 'struct', 'hybrid'][p.GetInt("ifcExportModel", 0)]
|
||||
}
|
||||
if hasattr(ifcopenshell,"schema_identifier"):
|
||||
|
||||
if hasattr(ifcopenshell, "schema_identifier"):
|
||||
schema = ifcopenshell.schema_identifier
|
||||
elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6):
|
||||
elif hasattr(ifcopenshell, "version") and (float(ifcopenshell.version[:3]) >= 0.6):
|
||||
# v0.6 onwards allows to set our own schema
|
||||
schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)]
|
||||
schema = ["IFC4", "IFC2X3"][p.GetInt("IfcVersion", 0)]
|
||||
else:
|
||||
schema = "IFC2X3"
|
||||
|
||||
preferences["SCHEMA"] = schema
|
||||
|
||||
return preferences
|
||||
|
||||
|
||||
# ************************************************************************************************
|
||||
# ********** export IFC ****************
|
||||
def export(exportList,filename,colors=None,preferences=None):
|
||||
|
||||
"""export(exportList,filename,colors=None,preferences=None) -- exports FreeCAD contents to an IFC file.
|
||||
colors is an optional dictionary of objName:shapeColorTuple or objName:diffuseColorList elements
|
||||
to be used in non-GUI mode if you want to be able to export colors."""
|
||||
def export(exportList, filename, colors=None, preferences=None):
|
||||
"""Export the selected objects to IFC format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
colors:
|
||||
It defaults to `None`.
|
||||
It is an optional dictionary of `objName:shapeColorTuple`
|
||||
or `objName:diffuseColorList` elements to be used in non-GUI mode
|
||||
if you want to be able to export colors.
|
||||
"""
|
||||
try:
|
||||
global ifcopenshell
|
||||
import ifcopenshell
|
||||
except:
|
||||
FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
|
||||
FreeCAD.Console.PrintMessage("Visit https://www.freecadweb.org/wiki/Arch_IFC to learn how to install it\n")
|
||||
except ModuleNotFoundError:
|
||||
_err("IfcOpenShell was not found on this system. "
|
||||
"IFC support is disabled.\n"
|
||||
"Visit https://wiki.freecadweb.org/IfcOpenShell "
|
||||
"to learn about installing it.")
|
||||
return
|
||||
|
||||
starttime = time.time()
|
||||
|
||||
if preferences is None:
|
||||
preferences = getPreferences()
|
||||
|
||||
# process template
|
||||
|
||||
version = FreeCAD.Version()
|
||||
owner = FreeCAD.ActiveDocument.CreatedBy
|
||||
email = ''
|
||||
@@ -188,28 +209,34 @@ def export(exportList,filename,colors=None,preferences=None):
|
||||
s = owner.split("<")
|
||||
owner = s[0].strip()
|
||||
email = s[1].strip(">")
|
||||
|
||||
global template
|
||||
template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2])
|
||||
if preferences['DEBUG']: print("Exporting an",preferences['SCHEMA'],"file...")
|
||||
template = template.replace("$ifcschema",preferences['SCHEMA'])
|
||||
template = template.replace("$owner",owner)
|
||||
template = template.replace("$company",FreeCAD.ActiveDocument.Company)
|
||||
template = template.replace("$email",email)
|
||||
template = template.replace("$now",str(int(time.time())))
|
||||
template = template.replace("$filename",os.path.basename(filename))
|
||||
template = template.replace("$timestamp",str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())))
|
||||
if hasattr(ifcopenshell,"version"):
|
||||
template = template.replace("IfcOpenShell","IfcOpenShell "+ifcopenshell.version)
|
||||
templatefilehandle,templatefile = tempfile.mkstemp(suffix=".ifc")
|
||||
of = pyopen(templatefile,"w")
|
||||
template = ifctemplate.replace("$version",
|
||||
version[0] + "."
|
||||
+ version[1] + " build " + version[2])
|
||||
if preferences['DEBUG']: print("Exporting an", preferences['SCHEMA'], "file...")
|
||||
template = template.replace("$ifcschema", preferences['SCHEMA'])
|
||||
template = template.replace("$owner", owner)
|
||||
template = template.replace("$company", FreeCAD.ActiveDocument.Company)
|
||||
template = template.replace("$email", email)
|
||||
template = template.replace("$now", str(int(time.time())))
|
||||
template = template.replace("$filename", os.path.basename(filename))
|
||||
template = template.replace("$timestamp",
|
||||
str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())))
|
||||
if hasattr(ifcopenshell, "version"):
|
||||
template = template.replace("IfcOpenShell",
|
||||
"IfcOpenShell " + ifcopenshell.version)
|
||||
templatefilehandle, templatefile = tempfile.mkstemp(suffix=".ifc")
|
||||
of = pyopen(templatefile, "w")
|
||||
|
||||
if six.PY2:
|
||||
template = template.encode("utf8")
|
||||
|
||||
of.write(template)
|
||||
of.close()
|
||||
os.close(templatefilehandle)
|
||||
|
||||
# create IFC file
|
||||
|
||||
global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs
|
||||
ifcfile = ifcopenshell.open(templatefile)
|
||||
ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"])
|
||||
@@ -1502,6 +1529,10 @@ def export(exportList,filename,colors=None,preferences=None):
|
||||
print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%")
|
||||
del ifcbin
|
||||
|
||||
endtime = time.time() - starttime
|
||||
|
||||
_msg("Finished exporting in {} seconds".format(int(endtime)))
|
||||
|
||||
|
||||
# ************************************************************************************************
|
||||
# ********** helper for export IFC **************
|
||||
|
||||
@@ -44,7 +44,7 @@ import ArchIFCSchema
|
||||
import importIFCHelper
|
||||
import importIFCmulticore
|
||||
|
||||
from draftutils.messages import _err
|
||||
from draftutils.messages import _msg, _err
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -144,9 +144,8 @@ structuralifcobjects = (
|
||||
)
|
||||
|
||||
|
||||
# Preferences available in import and export
|
||||
def getPreferences():
|
||||
"""Retrieve the IFC preferences.
|
||||
"""Retrieve the IFC preferences available in import and export.
|
||||
|
||||
MERGE_MODE_ARCH:
|
||||
0 = parametric arch objects
|
||||
@@ -200,6 +199,9 @@ def export(exportList, filename, colors=None, preferences=None):
|
||||
def open(filename, skip=[], only=[], root=None):
|
||||
"""Open an IFC file inside a new document.
|
||||
|
||||
TODO: change the default argument to `None`, instead of `[]`.
|
||||
This is better because lists are mutable.
|
||||
|
||||
Most of the work is done in the `insert` function.
|
||||
"""
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
@@ -213,6 +215,9 @@ def open(filename, skip=[], only=[], root=None):
|
||||
def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
"""Import the contents of an IFC file in the current active document.
|
||||
|
||||
TODO: change the default argument to `None`, instead of `[]`.
|
||||
This is better because lists are mutable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
skip: list
|
||||
@@ -228,18 +233,17 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
It is used to import only the derivates of a certain element type,
|
||||
for example, `'ifcProduct'
|
||||
"""
|
||||
starttime = time.time() # in seconds
|
||||
|
||||
# read preference settings
|
||||
if preferences is None:
|
||||
preferences = getPreferences()
|
||||
|
||||
if preferences["MULTICORE"] and not hasattr(srcfile, "by_guid"):
|
||||
return importIFCmulticore.insert(srcfile, docname, preferences)
|
||||
|
||||
try:
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import geom
|
||||
|
||||
# Sometimes there is an error importing `geom` in this way
|
||||
# import ifcopenshell.geom
|
||||
#
|
||||
# therefore we must use the `from x import y` way.
|
||||
#
|
||||
# For some reason this works; see the bug report
|
||||
# https://github.com/IfcOpenShell/IfcOpenShell/issues/689
|
||||
except ModuleNotFoundError:
|
||||
_err("IfcOpenShell was not found on this system. "
|
||||
"IFC support is disabled.\n"
|
||||
@@ -247,6 +251,14 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
"to learn about installing it.")
|
||||
return
|
||||
|
||||
starttime = time.time() # in seconds
|
||||
|
||||
if preferences is None:
|
||||
preferences = getPreferences()
|
||||
|
||||
if preferences["MULTICORE"] and not hasattr(srcfile, "by_guid"):
|
||||
return importIFCmulticore.insert(srcfile, docname, preferences)
|
||||
|
||||
try:
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
except NameError:
|
||||
@@ -264,28 +276,30 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
# keeping global variable for debugging purposes
|
||||
# global ifcfile
|
||||
|
||||
# If the `by_guid` attribute exists, this is already a loaded ifcfile,
|
||||
# otherwise, it's just a string, and we have to open it with ifcopenshell
|
||||
if hasattr(srcfile, "by_guid"):
|
||||
ifcfile = srcfile
|
||||
filesize = None
|
||||
filename = None
|
||||
else:
|
||||
if preferences['DEBUG']: print("Opening ",srcfile,"...",end="")
|
||||
if preferences['DEBUG']: print("Opening ", srcfile, "...", end="")
|
||||
filename = importIFCHelper.decode(srcfile, utf=True)
|
||||
filesize = os.path.getsize(filename) * 0.000001 # in megabytes
|
||||
filesize = os.path.getsize(filename) * 1E-6 # in megabytes
|
||||
ifcfile = ifcopenshell.open(filename)
|
||||
|
||||
# get file scale
|
||||
# Get file scale
|
||||
ifcscale = importIFCHelper.getScaling(ifcfile)
|
||||
|
||||
# IfcOpenShell multiplies the precision value of the file by 100
|
||||
# So we raise the precision by 100 too to compensate...
|
||||
# So we raise the precision by 100 too to compensate.
|
||||
# ctxs = ifcfile.by_type("IfcGeometricRepresentationContext")
|
||||
# for ctx in ctxs:
|
||||
# if not ctx.is_a("IfcGeometricRepresentationSubContext"):
|
||||
# ctx.Precision = ctx.Precision/100
|
||||
|
||||
# set default ifcopenshell options to work in brep mode
|
||||
settings = ifcopenshell.geom.settings()
|
||||
# Set default ifcopenshell options to work in brep mode
|
||||
settings = geom.settings()
|
||||
settings.set(settings.USE_BREP_DATA, True)
|
||||
settings.set(settings.SEW_SHELLS, True)
|
||||
settings.set(settings.USE_WORLD_COORDS, True)
|
||||
@@ -302,22 +316,27 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
floors = ifcfile.by_type("IfcBuildingStorey")
|
||||
openings = ifcfile.by_type("IfcOpeningElement")
|
||||
materials = ifcfile.by_type("IfcMaterial")
|
||||
products, annotations = importIFCHelper.buildRelProductsAnnotations(ifcfile, preferences['ROOT_ELEMENT'])
|
||||
(products,
|
||||
annotations) = importIFCHelper.buildRelProductsAnnotations(ifcfile,
|
||||
preferences['ROOT_ELEMENT'])
|
||||
|
||||
# empty relation tables
|
||||
objects = {} # { id:object, ... }
|
||||
shapes = {} # { id:shaoe } only used for merge mode
|
||||
structshapes = {} # { id:shaoe } only used for merge mode
|
||||
sharedobjects = {} # { representationmapid:object }
|
||||
parametrics = [] # a list of imported objects whose parametric
|
||||
# relationships need processing after all objects
|
||||
# have been created
|
||||
|
||||
# a list of imported objects whose parametric relationships
|
||||
# need processing after all objects have been created
|
||||
parametrics = []
|
||||
profiles = {} # to store reused extrusion profiles {ifcid:fcobj, ...}
|
||||
layers = {} # { layer_name, [ids] }
|
||||
# filled relation tables
|
||||
|
||||
# TODO for the following tables might be better use inverse attributes,
|
||||
# done for properties
|
||||
# TODO: investigate using inverse attributes.
|
||||
# For the following tables it might be better to use inverse attributes
|
||||
# to find the properties, otherwise a lot of loops
|
||||
# and if testing is needed.
|
||||
# See https://forum.freecadweb.org/viewtopic.php?f=39&t=37892
|
||||
prodrepr = importIFCHelper.buildRelProductRepresentation(ifcfile)
|
||||
additions = importIFCHelper.buildRelAdditions(ifcfile)
|
||||
@@ -480,7 +499,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
else:
|
||||
settings.set(settings.INCLUDE_CURVES,False)
|
||||
try:
|
||||
cr = ifcopenshell.geom.create_shape(settings,product)
|
||||
cr = geom.create_shape(settings, product)
|
||||
brep = cr.geometry.brep_data
|
||||
except:
|
||||
pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care, we're brave enough
|
||||
@@ -1335,8 +1354,11 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None):
|
||||
endtime = time.time()-starttime
|
||||
|
||||
if filesize:
|
||||
print("Finished importing",round(filesize,1),"Mb in",int(endtime),"seconds, or",int(endtime/filesize),"s/Mb")
|
||||
_msg("Finished importing {0} MB "
|
||||
"in {1} seconds, or {2} s/MB".format(round(filesize, 1),
|
||||
int(endtime),
|
||||
int(endtime/filesize)))
|
||||
else:
|
||||
print("Finished importing in",int(endtime),"seconds")
|
||||
_msg("Finished importing in {} seconds".format(int(endtime)))
|
||||
|
||||
return doc
|
||||
|
||||
Reference in New Issue
Block a user