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:
vocx-fc
2020-08-05 00:05:48 -05:00
committed by Yorik van Havre
parent 024ae4dd9f
commit c1476cd96f
2 changed files with 169 additions and 116 deletions

View File

@@ -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 **************

View File

@@ -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