update mager

This commit is contained in:
admin
2021-01-21 13:43:19 +08:00
parent 555a2ce79e
commit 371e4137f1
204 changed files with 33471 additions and 2737 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." O
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
OPTION(IFCCONVERT_DOUBLE_PRECISION "IfcConvert: Use double precision floating-point numbers." ON)
OPTION(BUILD_IFCGEOM "Build IfcGeom." ON)
OPTION(BUILD_IFCPYTHON "Build IfcPython." ON)
OPTION(BUILD_IFCPYTHON "Build IfcPython." OFF)
OPTION(BUILD_EXAMPLES "Build example applications." ON)
OPTION(BUILD_GEOMSERVER "Build IfcGeomServer executable." ON)
OPTION(BUILD_CONVERT "Build IfcConvert executable." ON)
+53
View File
@@ -0,0 +1,53 @@
# ****************************************************************************
# line endings
# to suppress line ending changes in github, add ?w=1 at link end of a diff
# for more information see
# FreeCAD source code src/Mod/.gitattributes
# FreeCAD forum topic https://forum.freecadweb.org/viewtopic.php?f=17&t=41117
# FreeCAD pull request https://github.com/FreeCAD/FreeCAD/pull/2752
# get all used file types
# in a directory in a bash use
# find . -type f -name '*.*' | sed 's|.*\.||' | sort -u
# search for a specific file ending
# find . -type f -name '*.ico'
# normalize the line endings of the following files
# standard files
*.feature text
*.html text
*.ifc text
*.md text
*.po text
*.pot text
*.py text
# files which are human readable
# but for which it is not sure if normalize is ok
# svg
# binary files
# ico
# mo
# line endings of the directories commented will be normalized
# bimtester/** -text
# examples/** -text
# line endings of the directories NOT commented will NOT be normalized
# Be carefully changes here could affect a lot of files automatically!
# none ATM
# use git to manually correct the file endings
# git add --renormalize .
@@ -15,8 +15,11 @@ def before_all(context):
# get from userdata
userdata = context.config.userdata
context.ifcbasename = userdata["ifcbasename"]
context.localedir = userdata.get("localedir")
context.ifcfile = userdata["ifcfile"]
context.ifcbasename = os.path.basename(
os.path.splitext(context.ifcfile)[0]
)
# do not break after a failed scenario
# https://community.osarch.org/discussion/comment/3328/#Comment_3328
@@ -0,0 +1,30 @@
import json
def create_logfile(thelogfile, ifcbasename):
logfile = open(thelogfile, "w")
logfile.write("BIMTester log file\n")
logfile.write("------------------\n\n")
logfile.write("ifc base file name: {}\n".format(ifcbasename))
logfile.close()
def append_logfile(thecontext, step):
# step attributes (also these set by user) scope is the scenario
# https://behave.readthedocs.io/en/latest/thecontext_attributes.html
print("Step '{}' failed".format(step.name))
# log file
logfile = open(thecontext.thelogfile, "a")
logfile.write("\n\nStep '{}' failed\n".format(step.name))
if hasattr(thecontext, "falseelems"):
logfile.write("{}\n".format(
json.dumps(thecontext.falseelems, indent=4)
))
if hasattr(thecontext, "falseprops"):
logfile.write("{}\n".format(
json.dumps(thecontext.falseprops, indent=4)
))
logfile.close()
@@ -9,17 +9,25 @@ from utils import switch_locale
the_lang = "en"
@step("there are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
@step("There are no {ifc_class} elements")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass_because_reason(
aem.no_eleclass(
context,
ifc_class,
reason
ifc_class
)
@step('all {ifc_class} elements class attributes have a value')
@step("There are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
@step("All {ifc_class} elements class attributes have a value")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
@@ -28,7 +36,7 @@ def step_impl(context, ifc_class):
)
@step('all {ifc_class} elements have a name given')
@step("All {ifc_class} elements have a name given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
@@ -37,7 +45,7 @@ def step_impl(context, ifc_class):
)
@step('all {ifc_class} elements have a description given')
@step("All {ifc_class} elements have a description given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
@@ -0,0 +1,52 @@
from behave import step
import attributes_eleclasses_methods as aem
from utils import switch_locale
the_lang = "de"
@step("Es sind keine {ifc_class} Bauteile vorhanden")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
@step("Aus folgendem Grund gibt es keine {ifc_class} Bauteile: {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
@step("Alle {ifc_class} Bauteilklassenattribute haben einen Wert")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
context,
ifc_class
)
@step("Bei allen {ifc_class} Bauteile ist der Name angegeben")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
context,
ifc_class
)
@step("Bei allen {ifc_class} Bauteile ist die Beschreibung angegeben")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
@@ -0,0 +1,59 @@
from behave import step
import attributes_eleclasses_methods as aem
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("There are no {ifc_class} elements")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
# TODO the next line needs translation
@step("There are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements class attributes have a value")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements have a name given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements have a description given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
"""
@@ -4,8 +4,8 @@ from utils import assert_elements
from utils import IfcFile
def no_eleclass_because_reason(
context, ifc_class, reason
def no_eleclass(
context, ifc_class
):
context.falseelems = []
@@ -0,0 +1,18 @@
from behave import step
import attributes_psets_methods as apm
from utils import switch_locale
the_lang = "de"
@step("An alle {ifc_class} Bauteile ist im PSet {pset} das Attribut {aproperty} angehängt")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, the_lang)
apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
@@ -0,0 +1,21 @@
from behave import step
import attributes_psets_methods as apm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("All {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, the_lang)
apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
"""
@@ -0,0 +1,20 @@
from behave import step
import geometric_detail_methods as gdm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("all {ifc_class} elements have an {representation_class} representation")
def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, the_lang)
gdm.eleclass_has_geometric_representation_of_specific_class(
context,
ifc_class,
representation_class
)
"""
@@ -2,7 +2,7 @@ import gettext # noqa
from behave import given
from behave import step
from ifcdata_methods import assert_schema
import ifcdata_methods as idm
from utils import IfcFile
from utils import switch_locale
@@ -28,12 +28,10 @@ def step_impl(context, file):
assert False, f"The file {file} could not be loaded"
@given('The IFC file has been provided through an argument')
@given("The IFC file has been provided through an argument")
def step_impl(context):
try:
IfcFile.load(context.config.userdata.get("ifcfile"))
except:
assert False, f"The IFC {context.config.userdata.get('ifcfile')} file could not be loaded"
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@given('A file path has been provided through an argument')
@@ -47,7 +45,7 @@ def step_impl(context):
@step("IFC data must use the {schema} schema")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
assert_schema(context, schema)
idm.has_ifcdata_specific_schema(context, schema)
@step('The IFC file "{file}" is exempt from being provided')
@@ -1,13 +1,19 @@
from behave import step
from ifcdata_methods import assert_schema
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "de"
@step("Die IFC Daten müssen das {schema} Schema benutzen")
@given("Die IFC-Datei wurde durch einen Startparameter zur Verfügung gestellt")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("Die IFC-Daten müssen das {schema} Schema benutzen")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
assert_schema(context, schema)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,13 +1,22 @@
from behave import step
from ifcdata_methods import assert_schema
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("Les données IFC doivent utiliser le schéma {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
assert_schema(context, schema)
idm.has_ifcdata_specific_schema(context, schema)
@@ -0,0 +1,19 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "it"
@given("Il file IFC è stato fornito attraverso un argumento")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("I dati IFC devono seguire lo schema {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,7 +1,17 @@
from utils import IfcFile
def assert_schema(context, target_schema):
def provide_ifcfile_by_argument(context):
try:
IfcFile.load(context.config.userdata.get("ifcfile"))
except:
assert False, (
_("The IFC {} file could not be loaded")
.format(context.config.userdata.get('ifcfile'))
)
def has_ifcdata_specific_schema(context, target_schema):
real_schema = IfcFile.get().schema
assert real_schema == target_schema, (
_("We expected a schema of {} but instead got {}")
@@ -0,0 +1,22 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "nl"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("IFC-gegevens moeten het {schema} -schema gebruiken")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -0,0 +1,20 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "de"
@step("Die Globale Identifikationskennung (Globally Unique Identifier = GUID) des Projektes ist {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
@step('Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -0,0 +1,23 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
@step('The project name, code, or short identifier must be "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
"""
@@ -0,0 +1,23 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "it"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
"""
@step('Il nome del progetto, codice o identificatore breve deve essere "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -0,0 +1,23 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "nl"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
"""
@step('De projectnaam, code of korte ID moet "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -0,0 +1,138 @@
import fileinput
def create_zoom_smartview(sm_file, ifcbasename):
smf = open(sm_file, "w")
smf.write('<?xml version="1.0"?>\n')
smf.write("<bimcollabsmartviewfile>\n")
smf.write(" <version>5</version>\n")
smf.write(" <applicationversion>Win - Version: ")
# next line belongs to last, because of line length
smf.write("3.4 (build 3.4.13.559)</applicationversion>\n")
smf.write("</bimcollabsmartviewfile>\n")
smf.write("\n")
smf.write("<SMARTVIEWSETS>\n")
smf.write(" <SMARTVIEWSET>\n")
smf.write(" <TITLE>BIMTester {}</TITLE>\n".format(ifcbasename))
smf.write(" <DESCRIPTION></DESCRIPTION>\n")
smf.write(" <GUID>a2ddfaf7-97f2-4519-aabd-f2d94f6b4d6b</GUID>\n")
smf.write(" <MODIFICATIONDATE>2020-10-30T13:23:30")
# next line belongs to last, because of line length
smf.write("</MODIFICATIONDATE>\n")
smf.write(" <SMARTVIEWS>\n")
smf.write(" </SMARTVIEWS>\n")
smf.write(" </SMARTVIEWSET>\n")
smf.write("</SMARTVIEWSETS>\n")
smf.close()
def append_zoom_smartview(sm_file, step_name, false_elements_guid):
# build the smartview string
smview_string = " <SMARTVIEW>\n"
smview_string += (
" <TITLE>GUID filter, {}</TITLE>\n"
.format(step_name)
)
smview_string += "{}\n".format(each_smartview_string_before)
for guid in false_elements_guid:
smview_string += (
"{}{}{}\n".format(
rule_string_before,
guid,
rule_string_after)
)
smview_string += "{}\n".format(each_smartview_string_after)
# insert smartview string into file
theline = " </SMARTVIEWS>"
newtext = smview_string + theline
for line in fileinput.FileInput(sm_file, inplace=True):
# the print replaces the line in the file
# and add the line afterwards
print(line.replace(theline, newtext), end="")
each_smartview_string_title = """ <SMARTVIEW>
<TITLE>Filter GUID</TITLE>
<DESCRIPTION></DESCRIPTION>"""
each_smartview_string_before = """ <CREATOR>bernd@bimstatik.ch</CREATOR>
<CREATIONDATE>2020-10-30T13:18:45</CREATIONDATE>
<MODIFIER>bernd@bimstatik.ch</MODIFIER>
<MODIFICATIONDATE>2020-10-30T13:23:30</MODIFICATIONDATE>
<GUID>15fda94f-b4bf-43be-8ef4-15d3121137e1</GUID>
<RULES>
<RULE>
<IFCTYPE>Any</IFCTYPE>
<PROPERTY>
<NAME>None</NAME>
<PROPERTYSETNAME>None</PROPERTYSETNAME>
<TYPE>None</TYPE>
<VALUETYPE>None</VALUETYPE>
<UNIT>None</UNIT>
</PROPERTY>
<CONDITION>
<TYPE>Is</TYPE>
<VALUE></VALUE>
</CONDITION>
<ACTION>
<TYPE>AddSetColored</TYPE>
<R>187</R>
<G>187</G>
<B>187</B>
</ACTION>
</RULE>
<RULE>
<IFCTYPE>Any</IFCTYPE>
<PROPERTY>
<NAME>None</NAME>
<PROPERTYSETNAME>None</PROPERTYSETNAME>
<TYPE>None</TYPE>
<VALUETYPE>None</VALUETYPE>
<UNIT>None</UNIT>
</PROPERTY>
<CONDITION>
<TYPE>Is</TYPE>
<VALUE></VALUE>
</CONDITION>
<ACTION>
<TYPE>SetTransparent</TYPE>
</ACTION>
</RULE>"""
each_smartview_string_after = """ </RULES>
<INFORMATIONTAKEOFF>
<PROPERTYSETNAME>None</PROPERTYSETNAME>
<PROPERTYNAME>None</PROPERTYNAME>
<OPERATION>0</OPERATION>
</INFORMATIONTAKEOFF>
</SMARTVIEW>"""
rule_string_before = """ <RULE>
<IFCTYPE>Any</IFCTYPE>
<PROPERTY>
<NAME>GUID</NAME>
<PROPERTYSETNAME>Summary</PROPERTYSETNAME>
<TYPE>Summary</TYPE>
<VALUETYPE>StringValue</VALUETYPE>
<UNIT>None</UNIT>
</PROPERTY>
<CONDITION>
<TYPE>Is</TYPE>
<VALUE>"""
rule_string_after = """</VALUE>
</CONDITION>
<ACTION>
<TYPE>SetColored</TYPE>
<R>255</R>
<G>10</G>
<B>10</B>
</ACTION>
</RULE>"""
+59 -23
View File
@@ -1,6 +1,9 @@
# TODO: improve layout, start with feature file path and beside button !!!!!
# TODO: all args should be passed to the gui,
# either pass them further to behave or make it possible to edit them before
# TODO: if browse widgets will be canceled, last QLineEdit should be restored
# TODO: keep path or file if in browse widget canceled
# TODO: make a frame around features direcory chooser and
# use features path from ifc button
import os
@@ -15,25 +18,32 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def __init__(
self,
features="",
ifcfile=""
featurespath="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
super(GuiWidgetBimTester, self).__init__()
# get features dir
user_path = os.path.expanduser("~")
# print(features)
self.initial_featurespath = features
if not os.path.isdir(self.initial_featurespath):
self.initial_featurespath = user_path
print(self.initial_featurespath)
# get features dir
# print(featurespath)
if not os.path.isdir(featurespath):
featurespath = user_path
# get ifc file
# print(ifcfile)
if not os.path.isfile(ifcfile):
ifcfile = user_path
self.initial_featurespath = featurespath
self.initial_ifcfile = ifcfile
if not os.path.isfile(self.initial_ifcfile):
self.initial_ifcfile = user_path
print(self.initial_ifcfile)
self.get_featurepath_from_ifcpath = get_featurepath_from_ifcpath
self.args = args
# print(self.initial_featurespath)
# print(self.initial_ifcfile)
# print(self.get_featurepath_from_ifcpath)
# init ui
self._setup_ui()
@@ -143,6 +153,9 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.setRowStretch(0, 10)
self.setLayout(layout)
# set button state, needs to be here after gui has beed set up
self.featuredirfromifc_cb.setChecked(self.get_featurepath_from_ifcpath)
# **********************************************************
def select_ifcfile(self):
# print(self.get_ifcfile())
@@ -162,11 +175,8 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def featuredirfromifc_clicked(self):
if self.featuredirfromifc_cb.isChecked() is True:
self.set_featurefilesdir("")
# TODO
self.featurefilesdir_text.setEnabled(False)
self.feafilesdir_browse_btn.setEnabled(False)
# deactivate feature path browser button
# deactivate lineedit text
else:
self.set_featurefilesdir(self.initial_featurespath)
self.featurefilesdir_text.setEnabled(True)
@@ -193,18 +203,42 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# **********************************************************
def run_bimtester(self):
print("Run BIMTester")
print("Run BIMTester by the GUI")
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
# get features dir
if self.featuredirfromifc_cb.isChecked() is True:
the_features_path = os.path.dirname(os.path.realpath(
self.get_ifcfile()
))
ifcfile = self.get_ifcfile()
print(
"Make sure the feature files are beside "
"the ifc file in a directory named 'features'."
)
if ifcfile == "":
# os.path.realpath("") would return the cmd dir and not ""
print(
"No ifcfile given, "
"thus features files path will be set to ''."
)
the_features_path = ""
elif os.path.isfile(ifcfile) is not True:
# if ifcfile does not exist set features path to ""
print(
"The ifcfile does not exist, "
"thus features files path will be set to ''."
)
the_features_path = ""
else:
ifcfilepath = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifcfilepath):
the_features_path = ifcfilepath
else:
print(
"ifcfilepath does not exist, "
"thus features files path will be set to ''."
"this shold never happen, please debug. "
)
the_features_path = ""
else:
the_features_path = self.get_featurefilesdir()
print(the_features_path)
@@ -213,11 +247,13 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
the_ifcfile = self.get_ifcfile()
print(the_ifcfile)
# overwrite the_features_path and ifcfile in args
patched_args = self.args
patched_args["featuresdir"] = the_features_path
patched_args["ifcfile"] = the_ifcfile
# run bimtester
status = run_all(
the_features_path,
the_ifcfile,
)
status = run_all(patched_args)
print(status)
QtWidgets.QApplication.restoreOverrideCursor()
@@ -7,150 +7,158 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: 2020-12-23 11:20+0100\n"
"Last-Translator: \n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr "OpenBIM auditing ist eine Funktionalität von"
#: reports.py:155
#: reports.py:158
msgid "en"
msgstr "de"
#: reports.py:156
#: reports.py:159
msgid "Success"
msgstr "Bestanden"
#: reports.py:157
#: reports.py:160
msgid "Failure"
msgstr "Durchgefallen"
#: reports.py:158
#: reports.py:161
msgid "Tests passed"
msgstr "Erfolgreiche Tests"
#: reports.py:159
#: reports.py:162
msgid "Duration"
msgstr "Dauer"
#: reports.py:161
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr "OpenBIM auditing ist eine Funktionalität von"
#: reports.py:164
msgid "and"
msgstr "und"
#: features/steps/attributes_eleclasses_methods.py:24
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr "Fehler in falsecount, es ist etwas falsch gelaufen."
#: features/steps/attributes_eleclasses_methods.py:80
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes "
"{parameter} has no value."
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least "
"one of these class attributes {parameter} has no value: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr "Es sind keine {ifc_class} Bauteile in der IFC-Datei."
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: "
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr "Es sind keine {ifc_class} Bauteile in der IFC-Datei."
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements is not "
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the "
"property {parameter} in the pset: {falseelems}"
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
"Alle {elemcount} {ifc_class} Bauteile haben keine geometrische Repräsentation "
"der Klasse {parameter}."
"Alle {elemcount} {ifc_class} Bauteile haben keine geometrische "
"Repräsentation der Klasse {parameter}."
#: features/steps/geometric_detail_methods.py:51
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
"Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen "
"haben keine geometrische Repräsentation der Klasse {parameter}: {falseelems}"
"Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} "
"Bauteilen haben keine geometrische Repräsentation der Klasse {parameter}:"
" {falseelems}"
#: features/steps/geometric_detail_methods.py:125
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
"Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} Bauteilen "
"haben Fehler."
"Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} "
"Bauteilen haben Fehler."
#: features/steps/geometric_detail_methods.py:126
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have "
"errors: {falseelems}"
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
"Die geometrischen Repräsentationen von {falsecount} von allen {elemcount} "
"{ifc_class} Bauteilen haben Fehler: {falseelems}"
"Die geometrischen Repräsentationen von {falsecount} von allen {elemcount}"
" {ifc_class} Bauteilen haben Fehler: {falseelems}"
#: features/steps/ifcdata_methods.py:7
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}"
#~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr ""
#~ "Die geometrischen Repräsentationen von allen {} {} Bauteilen haben Fehler."
#~ "Die geometrischen Repräsentationen von allen"
#~ " {} {} Bauteilen haben Fehler."
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr ""
#~ "Die geometrischen Repräsentationen von {} von allen {} {} Bauteilen haben "
#~ "Die geometrischen Repräsentationen von {} "
#~ "von allen {} {} Bauteilen haben "
#~ "Fehler: {}"
#~ msgid "There are no {} elements in the IFC file."
@@ -158,26 +166,35 @@ msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}"
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr ""
#~ "Alle {} {} Bauteile haben keine geometrische Repräsentation der Klasse "
#~ "Alle {} {} Bauteile haben keine "
#~ "geometrische Repräsentation der Klasse "
#~ "IfcFacetedBrep."
#~ msgid ""
#~ "The following {} of {} {} elements are not a IfcFacetedBrep representation: "
#~ "The following {} of {} {} elements"
#~ " are not a IfcFacetedBrep representation:"
#~ " {}"
#~ msgstr ""
#~ "Die Anzahl Bauteile {} von allen "
#~ "{} {} Bauteilen haben keine geometrische"
#~ " Repräsentation der Klasse IfcFacetedBrep: "
#~ "{}"
#~ msgstr ""
#~ "Die Anzahl Bauteile {} von allen {} {} Bauteilen haben keine geometrische "
#~ "Repräsentation der Klasse IfcFacetedBrep: {}"
#~ msgid ""
#~ "All {elemcount} {ifc_class} elements are not a IfcFacetedBrep representation."
#~ "All {elemcount} {ifc_class} elements are "
#~ "not a IfcFacetedBrep representation."
#~ msgstr ""
#~ "Alle {elemcount} {ifc_class} Bauteile haben keine geometrische "
#~ "Repräsentation der Klasse IfcFacetedBrep."
#~ "Alle {elemcount} {ifc_class} Bauteile haben"
#~ " keine geometrische Repräsentation der "
#~ "Klasse IfcFacetedBrep."
#~ msgid ""
#~ "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
#~ "The following {falsecount} of {elemcount} "
#~ "{ifc_class} elements are not a "
#~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr ""
#~ "Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen "
#~ "haben keine geometrische Repräsentation der Klasse IfcFacetedBrep: "
#~ "{falseelems}"
#~ "Die Anzahl Bauteile {falsecount} von "
#~ "allen {elemcount} {ifc_class} Bauteilen haben"
#~ " keine geometrische Repräsentation der "
#~ "Klasse IfcFacetedBrep: {falseelems}"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
"Last-Translator: \n"
"Language: en\n"
@@ -18,122 +18,126 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
#: reports.py:158
msgid "en"
msgstr ""
#: reports.py:156
#: reports.py:159
msgid "Success"
msgstr ""
#: reports.py:157
#: reports.py:160
msgid "Failure"
msgstr ""
#: reports.py:158
#: reports.py:161
msgid "Tests passed"
msgstr ""
#: reports.py:159
#: reports.py:162
msgid "Duration"
msgstr ""
#: reports.py:161
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:164
msgid "and"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:24
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr ""
@@ -7,131 +7,137 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: 2020-12-23 11:21+0100\n"
"Last-Translator: \n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr "L'audit OpenBIM auditing est une fonctionnalité de"
#: reports.py:155
#: reports.py:158
msgid "en"
msgstr "fr"
#: reports.py:156
#: reports.py:159
msgid "Success"
msgstr "Succès"
#: reports.py:157
#: reports.py:160
msgid "Failure"
msgstr "Échec"
#: reports.py:158
#: reports.py:161
msgid "Tests passed"
msgstr "Tests réussis"
#: reports.py:159
#: reports.py:162
msgid "Duration"
msgstr "Durée"
#: reports.py:161
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr "L'audit OpenBIM auditing est une fonctionnalité de"
#: reports.py:164
msgid "and"
msgstr "et"
#: features/steps/attributes_eleclasses_methods.py:24
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes "
"{parameter} has no value."
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least "
"one of these class attributes {parameter} has no value: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: "
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements is not "
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the "
"property {parameter} in the pset: {falseelems}"
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have "
"errors: {falseelems}"
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr ""
@@ -0,0 +1,196 @@
# Italian translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
"Last-Translator: \n"
"Language: it\n"
"Language-Team: it <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: reports.py:158
msgid "en"
msgstr "it"
#: reports.py:159
msgid "Success"
msgstr "Successo"
#: reports.py:160
msgid "Failure"
msgstr "Errore"
#: reports.py:161
msgid "Tests passed"
msgstr "Test superati"
#: reports.py:162
msgid "Duration"
msgstr "Durata"
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr "L'auditing OpenBIM è una caratteristica di"
#: reports.py:164
msgid "and"
msgstr "e"
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "Tutti {elemcount} elementi nel file sono classificati {ifc_class}"
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
"{falsecount} di {elemcount} elementi sono elementi {ifc_class}: "
"{falseelems}"
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr "Errore in falsecount, qualcosa è andato storto."
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
"Per tutti {elemcount} elementi {ifc_class} almeno uno di questa "
"classedegli attributi {parameter} non contiene alcun valore"
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
"Per i seguenti {falsecount} di {elemcount} elementi {ifc_class}almeno uno"
" degli attributi {parameter} non contiene alcun valore"
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr "Il file IFC non contiene elementi della classe {ifc_class}."
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr "Il nome di {elemcount} {elemcount} elementi non è impostato"
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr "Il nome di {falsecount} su {elemcount} elementi {ifc_class} non èimpostato"
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr "La descrizione di tutti {elemcount} {elemcount} elementi non èimpostato "
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
"la descrizione di {falsecount} su {elemcount} elementi {ifc_class} non "
"èimpostato"
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
"A {elemcount} gli elementi {ifc_class} manca la proprietà "
"{parameter}nello pset."
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
"Ai seguenti {falsecount} di {elemcount} elementi {ifc_class} manca la "
"proprietà{parameter} nello pset: {falseelems}"
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr "Tutti {elemcount} elementi {ifc_class} non rappresentano {parameter}"
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
"I seguenti {falsecount} di {elemcount} elementi {ifc_class} non "
"rappresentano{parameter}: {falseelems}"
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr "La geometria di tutti {elemcount} elementi {ifc_class} contiene errori"
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
"La geometria di {falsecount} su {elemcount} elementi {ifc_class} "
"contieneerrori: {falseelems}"
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr "Ci aspettavamo uno schema di {} ma invece abbiamo ottenuto {}"
#~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr "La geometria di tutti gli elementi {} {} contiene errori."
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr "La geometria di {} su {} {} elementi contiene errori."
#~ msgid "There are no {} elements in the IFC file."
#~ msgstr "Non ci sono {} elementi nel file IFC."
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr "Tutti gli elementi {} {} non sono una rappresentazioneIfcFacetedBrep."
#~ msgid ""
#~ "The following {} of {} {} elements"
#~ " are not a IfcFacetedBrep representation:"
#~ " {}"
#~ msgstr ""
#~ "I seguenti {} di {} {} elementi"
#~ " non sono una rappresentazioneIfcFacetedBrep. "
#~ "{}"
#~ msgid ""
#~ "All {elemcount} {ifc_class} elements are "
#~ "not a IfcFacetedBrep representation."
#~ msgstr ""
#~ "Tutti {elemcount} elementi {ifc_class} non"
#~ " sono una rappresentazioneIfcFacetedBrep."
#~ msgid ""
#~ "The following {falsecount} of {elemcount} "
#~ "{ifc_class} elements are not a "
#~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr ""
#~ "I seguenti {falsecount} su {elemcount} "
#~ "elementi {ifc_class} non sonouna "
#~ "rappresentazione IfcFacetedBrep."
+39 -35
View File
@@ -1,14 +1,14 @@
# Translations template for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# Copyright (C) 2021 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,122 +17,126 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
#: reports.py:158
msgid "en"
msgstr ""
#: reports.py:156
#: reports.py:159
msgid "Success"
msgstr ""
#: reports.py:157
#: reports.py:160
msgid "Failure"
msgstr ""
#: reports.py:158
#: reports.py:161
msgid "Tests passed"
msgstr ""
#: reports.py:159
#: reports.py:162
msgid "Duration"
msgstr ""
#: reports.py:161
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:164
msgid "and"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:24
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr ""
@@ -0,0 +1,196 @@
# Dutch translations for Bimtester.
# Copyright (C) 2021 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# Marcel Plomp , 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSIE\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRES\n"
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
"Last-Translator: \n"
"Language: nl\n"
"Language-Team: nl <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: reports.py:158
msgid "en"
msgstr "nl"
#: reports.py:159
msgid "Success"
msgstr "Succes"
#: reports.py:160
msgid "Failure"
msgstr "Fout"
#: reports.py:161
msgid "Tests passed"
msgstr "Tests geslaagd"
#: reports.py:162
msgid "Duration"
msgstr "Tijdsduur"
#: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr "OpenBIM-auditing is een kenmerk van"
#: reports.py:164
msgid "and"
msgstr "en"
#: features/steps/attributes_eleclasses_methods.py:26
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "Alle {elemcount} de elementen in het bestand zijn {ifc_class}"
#: features/steps/attributes_eleclasses_methods.py:34
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
"{falsecount} van de {elemcount} elementen zijn {ifc_class} elementen: "
"{falseelems}"
#: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr "Fout in de telling, er is iets misgegaan."
#: features/steps/attributes_eleclasses_methods.py:84
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
"Voor alle {elemcount} {ifc_class} elementen heeft ten minste één van deze"
" class attributen {parameter} heeft geen waarde."
#: features/steps/attributes_eleclasses_methods.py:85
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
"Van de {falsecount} uit {elemcount} {ifc_class} elementen op ten minste "
"een van deze class-attributen {parameter} heeft geen waarde:{falseelems}"
#: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file."
msgstr "Er zijn geen {ifc_class} elementen in het IFC-bestand."
#: features/steps/attributes_eleclasses_methods.py:110
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr "De naam van alle {elemcount} {elemcount} elementen is niet ingesteld."
#: features/steps/attributes_eleclasses_methods.py:111
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
"De naam van {falsecount} van de {elemcount} {ifc_class} elementen is "
"nietset: {falseelems}"
#: features/steps/attributes_eleclasses_methods.py:137
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
"De beschrijving van alle {elemcount} {elemcount} elementen is niet "
"ingesteld."
#: features/steps/attributes_eleclasses_methods.py:138
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
"De beschrijving van {falsecount} uit {elemcount} {ifc_class} elementen is"
" niet ingesteld: {falseelems}"
#: features/steps/attributes_psets_methods.py:31
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
"Alle {elemcount} {ifc_class} elementen missen de eigenschap {parameter} "
"in de pset."
#: features/steps/attributes_psets_methods.py:32
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
"De volgende {falsecount} van {elemcount} {ifc_class} elementen zijn "
"ontbreekt de eigenschap {parameter} in de pset: {falseelems}"
#: features/steps/geometric_detail_methods.py:54
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
"Alle {elemcount} {ifc_class} elementen zijn geen {parameter} "
"representatie."
#: features/steps/geometric_detail_methods.py:55
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
"De volgende {falsecount} van {elemcount} {ifc_class} -elementen zijn geen"
" {parameter} representatie: {falseelems}"
#: features/steps/geometric_detail_methods.py:132
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
"De geometrie van alle {elemcount} de {ifc_class} elementen bevatten "
"fouten."
#: features/steps/geometric_detail_methods.py:133
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
"De geometrie van {falsecount} van alle {elemcount} {ifc_class} elementen "
"hebben fouten: {falseelems}"
#: features/steps/ifcdata_methods.py:9
msgid "The IFC {} file could not be loaded"
msgstr ""
#: features/steps/ifcdata_methods.py:17
msgid "We expected a schema of {} but instead got {}"
msgstr "We verwachtten een schema van {} maar kregen in plaats daarvan {}"
#~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr ""
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr ""
#~ msgid "There are no {} elements in the IFC file."
#~ msgstr ""
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr ""
#~ msgid ""
#~ "The following {} of {} {} elements"
#~ " are not a IfcFacetedBrep representation:"
#~ " {}"
#~ msgstr ""
#~ msgid ""
#~ "All {elemcount} {ifc_class} elements are "
#~ "not a IfcFacetedBrep representation."
#~ msgstr ""
#~ msgid ""
#~ "The following {falsecount} of {elemcount} "
#~ "{ifc_class} elements are not a "
#~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr ""
+24 -17
View File
@@ -8,12 +8,16 @@ from .features.steps.utils import switch_locale
def generate_report(
adir=".",
report_dir=".",
use_report_folder=True,
report_file_name="",
html_template_file_path=""
report_file_name="report.json",
html_template_file_path="",
report_file=""
):
# TODO use far less parameter
# to be discussed with other devs
print("# Generating HTML reports now.")
# get locale path
@@ -32,23 +36,22 @@ def generate_report(
if html_template_file_path:
report_template_path = html_template_file_path
# get report file
report_dir = adir
if use_report_folder:
report_dir = os.path.join(adir, "report")
if not os.path.exists(report_dir):
return print("No report directory was found.")
if report_file_name:
report_path = os.path.join(report_dir, report_file_name)
# get report file and report dir
if report_file:
report_file = report_file
report_dir = os.path.dirname(report_file)
else:
report_path = os.path.join(report_dir, "report.json")
# print(report_path)
if not os.path.exists(report_path):
return print("No report data was found.")
if use_report_folder:
report_dir = os.path.join(report_dir, "report")
report_file = os.path.join(report_dir, report_file_name)
# print(report_file)
if not os.path.isdir(report_dir):
return print("Report directory does not exist.")
if not os.path.isfile(report_file):
return print("Report file does not exist.")
# read json report and create html report for each feature
report = json.loads(open(report_path).read())
report = json.loads(open(report_file).read())
for feature in report:
file_name = os.path.basename(feature["location"]).split(":")[0]
data = {
@@ -134,6 +137,10 @@ def generate_report(
switch_locale(localedir, "de")
elif feature["keyword"] == "Fonctionnalité":
switch_locale(localedir, "fr")
elif feature["keyword"] == "Funzionalità":
switch_locale(localedir, "it")
elif feature["keyword"] == "Functionaliteit":
switch_locale(localedir, "nl")
else:
# standard English
switch_locale(localedir, "en")
+161 -147
View File
@@ -1,11 +1,13 @@
import behave.formatter.pretty # Needed for pyinstaller to package it
import fileinput
import os
import shutil
import sys
import tempfile
import webbrowser
from behave.__main__ import main as behave_main
# TODO: if the ifc file name or path contains special character
# like German Umlaute behave gives an error
# get bimtester source code module path
@@ -26,29 +28,106 @@ def get_resource_path(relative_path):
def run_tests(args):
if not get_features(args):
print("No features could be found to check.")
print("# Run tests.")
report_file = os.path.join("report", "report.json")
if "copyintemprun" in args and args["copyintemprun"] is True:
print("copyintemprun")
is_copyintemprun = True
args, copy_base_path = copy_intmp_tests(args)
features_path = os.path.join(copy_base_path, "features")
report_file = os.path.join(copy_base_path, report_file)
else:
print("No copyintemprun")
is_copyintemprun = False
if not get_features(args):
print("No features could be found to check.")
return False
features_path = get_resource_path("features")
# get behave args
behave_args = get_behave_args(args, features_path, report_file)
# run tests
if behave_args != []:
run_behave(behave_args)
else:
print("Error, not able to run behave because of empty behave args.")
return False
behave_args = [get_resource_path("features")]
if is_copyintemprun is True:
return report_file
else:
return True
def get_behave_args(args, features_path, report_file):
if os.path.isdir(features_path):
behave_args = [features_path]
else:
return []
if os.path.isdir(locale_path):
behave_args.extend([
# path for translation files
# next two lines are one arg
"--define",
"localedir={}".format(locale_path)
])
else:
print(
"Error, translation locals path '{}' does not exist."
.format(locale_path)
)
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
elif not args["console"]:
if args["ifcfile"]:
behave_args.extend([
# next two lines are one arg
"--define",
"ifcfile={}".format(args["ifcfile"])
])
if args["path"]:
behave_args.extend([
# next two lines are one arg
"--define",
"path={}".format(args["path"])
])
if not args["console"]:
behave_args.extend([
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
# https://github.com/behave/behave/issues/346
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# report file, if relative, than relative to current shell path
# next two lines are one arg
"--outfile",
"report/report.json"
report_file,
])
behave_args.extend([
"--define",
"localedir={}".format(locale_path)
])
if args["ifcfile"]:
behave_args.extend(["--define", "ifcfile={}".format(args["ifcfile"])])
if args["path"]:
behave_args.extend(["--define", "path={}".format(args["path"])])
return behave_args
def run_behave(behave_args):
from json import dumps
print(dumps(behave_args, indent=4))
from behave.__main__ import main as behave_main
behave_main(behave_args)
print("# All tests are finished.")
return True
@@ -99,41 +178,9 @@ reset_runtime()
"""
# TODO: if the ifc file name or path contains special character
# like German Umlaute behave gives an error
def copy_intmp_tests(args={}):
def run_intmp_tests(args={}):
"""
run bimtester unit test in a temporary directory
features, steps and environment.py are copied to a temp directory
Keys of parameter args
----------------------
features: optional (ATM mandatory)
the path the features directory with feature files is in
ifcfile: optional (ATM mandatory)
the ifc file
advanced_arguments: optional
they will be directly passed to the behave call
features and ifcfile are given:
the ifcfile in feature files is replaced
features only is given (TODO):
the ifcfile provided in the feature files is used
ifcfile only is given (TODO):
features = ifcfile directory
the ifcfile in feature files is replaced
none of both is given (TODO):
the current directory = features
the ifcfile provided in the feature files is used
TODO: if the above is implemented adapt signature of run_all
"""
print("# Copy features and steps to temp.")
from behave import __version__ as behave_version
# https://github.com/behave/behave/issues/871
@@ -144,53 +191,82 @@ def run_intmp_tests(args={}):
)
return False
# print(args)
# get the features_path, the dir where the feature files to test are in
if "features" in args and args["features"] != "":
the_features_path = os.path.join(args["features"], "features")
if ("featuresdir" in args and args["featuresdir"] != ""):
is_features = True
the_features_path = os.path.join(args["featuresdir"], "features")
if not os.path.isdir(the_features_path):
print(
"The features directory does not exist: {}"
"Error, the features directory '{}' does not exist."
.format(the_features_path)
)
return False
else:
# TODO assume features beside ifc thus use ifc path
print("No features path was given.")
return False
is_features = False
# get ifc path and ifc filename
if "ifcfile" in args and args["ifcfile"] != "":
if ("ifcfile" in args and args["ifcfile"] != ""):
is_ifcfile = True
ifcfile = args["ifcfile"]
if os.path.isfile(ifcfile) is not True:
print("Error, the ifc file '{}' does not exist.".format(ifcfile))
return False
ifc_path = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifc_path) is False:
print("ifc path does not exist.")
return False
if os.path.isfile(ifcfile) is True:
ifc_filename = os.path.basename(ifcfile)
else:
print("ifc file '{}' does not exist.".format(ifcfile))
return False
else:
# TODO use ifc path from feature files
print("No ifc file was given.")
is_ifcfile = False
# print(is_features)
# print(is_ifcfile)
if is_features is True and is_ifcfile is True:
print("features given, ifcfile given.")
elif is_features is False and is_ifcfile is True:
print("features given, ifcfile NOT given.")
# features = ifcfile directory
the_features_path = os.path.join(ifc_path, "features")
elif is_features is True and is_ifcfile is False:
print("features given, ifcfile NOT given.")
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
elif is_features is False and is_ifcfile is False:
print("features NOT given, ifcfile NOT given.")
# the current directory = features
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
else:
print("Error: this should never happen, please debug.")
return False
# set up paths
# a unique temp path should not be used
# behave raises an ambiguous step exception
# run_path = tempfile.mkdtemp()
# copy_base_path = tempfile.mkdtemp()
# thus use the same path on every run
# but delete it if exists
run_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(run_path):
copy_base_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(copy_base_path):
from shutil import rmtree
rmtree(run_path) # fails on read only files
if os.path.isdir(run_path):
print("Delete former beimtester run dir {} failed".format(run_path))
rmtree(copy_base_path) # fails on read only files
if os.path.isdir(copy_base_path):
print(
"Delete former beimtester run dir '{}' failed"
.format(copy_base_path)
)
return False
os.mkdir(run_path)
report_path = os.path.join(run_path, "report")
copy_features_path = os.path.join(run_path, "features")
os.mkdir(copy_base_path)
copy_features_path = os.path.join(copy_base_path, "features")
# copy features path from bimtester source code
srccode_features_path = os.path.join(
@@ -218,8 +294,7 @@ def run_intmp_tests(args={}):
# dirs_exist_ok=True
# )
# copy feature files and replace ifcpath in feature files
# replaceing is IMHO better than copy the ifc file which could be 500 MB
# copy feature files
feature_files = os.listdir(the_features_path)
# print(feature_files)
for feature_file in feature_files:
@@ -230,99 +305,38 @@ def run_intmp_tests(args={}):
os.path.join(the_features_path, feature_file),
cp_feature_file
)
# search the line
ff = open(cp_feature_file, "r")
lines = ff.readlines()
ff.close()
theline = ""
for line in lines:
if "* The IFC file" in line and "must be provided" in line:
theline = line
if ifc_filename is None:
ifc_filename = os.path.basename(theline.split('"')[1])
newifcline = (
' * The IFC file "{}" must be provided\n'
.format(os.path.join(ifc_path, ifc_filename))
)
# print(newifcline)
break
else:
print("The line which sets the ifc file to test was not found.")
newifcline = ""
# replace the line
if newifcline != "":
# https://stackoverflow.com/a/290494
for line in fileinput.input(cp_feature_file, inplace=True):
# the print replaces the line in the file
print(line.replace(theline, newifcline), end="")
# get advanced args
# print to console from inside step files, add "--no-capture" flag
# https://github.com/behave/behave/issues/346
behave_args = [copy_features_path]
if "advanced_arguments" in args:
behave_args.extend(args["advanced_arguments"].split())
elif "console" not in args:
behave_args.extend([
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# next two lines are one arg
"--outfile",
os.path.join(report_path, "report.json"),
# next two lines are one arg
"--define",
"ifcbasename={}".format(os.path.splitext(ifc_filename)[0]),
# next two lines are one arg
"--define",
"localedir={}".format(locale_path)
])
print(behave_args)
# run tests
from behave.__main__ import main as behave_main
behave_main(behave_args)
print("All tests are finished.")
# delete steps
# shutil.rmtree(steps_path)
return run_path
return args, copy_base_path
def run_all(the_features_path, the_ifcfile):
def run_all(args):
print("# Run all.")
# run bimtester
runpath = run_intmp_tests({
"features": the_features_path,
"ifcfile": the_ifcfile
})
print(runpath)
report_file = run_tests(args)
print(report_file)
# check if it worked out well
if runpath is False:
if report_file is False:
print("BIMTester behave tests returned False.")
return False
if not os.path.isdir(runpath):
print("runpath does not exist. This should not happen. Debug")
if not os.path.isfile(report_file):
print("Report directory does not exist. This should not happen. Debug")
return False
# create html report and open in webbrowser
from .reports import generate_report
generate_report(runpath)
generate_report(report_file=report_file)
# get the feature files
feature_files = os.listdir(
os.path.join(the_features_path, "features")
os.path.join(args["featuresdir"], "features")
)
# print(feature_files)
for ff in feature_files:
webbrowser.open(os.path.join(
runpath,
"report",
os.path.dirname(report_file),
ff + ".html"
))
@@ -0,0 +1,54 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('col.ifc','2021-01-11T05:16:43',('',''),(''),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.19 build 23652 (Git)','FreeCAD','118df2cf_ed21_438e_a41');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,1610342203,#3,#4,1610342203);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.,0.));
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCDIRECTION((0.,1.));
#21=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#20);
#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#21,$,.MODEL_VIEW.,$);
#23=IFCPROJECT('2iAYrakL9FABNNwZfj$CbO',#5,'BIMTester Example 1 - IFC2X3',$,$,$,$,(#21),#19);
#24=IFCDIRECTION((1.,0.));
#25=IFCCARTESIANPOINT((0.,0.));
#26=IFCAXIS2PLACEMENT2D(#25,#24);
#27=IFCCIRCLEPROFILEDEF(.AREA.,$,#26,0.2);
#28=IFCCARTESIANPOINT((0.,0.,0.));
#29=IFCAXIS2PLACEMENT3D(#28,#7,#6);
#30=IFCEXTRUDEDAREASOLID(#27,#29,#7,5.);
#31=IFCCOLOURRGB($,1.,0.5,1.);
#32=IFCSURFACESTYLERENDERING(#31,$,$,$,$,$,$,$,.FLAT.);
#33=IFCSURFACESTYLE($,.BOTH.,(#32));
#34=IFCPRESENTATIONSTYLEASSIGNMENT((#33));
#35=IFCSTYLEDITEM(#30,(#34),$);
#36=IFCLOCALPLACEMENT($,#9);
#37=IFCSHAPEREPRESENTATION(#22,'Body','SweptSolid',(#30));
#38=IFCPRODUCTDEFINITIONSHAPE($,$,(#37));
#39=IFCBUILDINGELEMENTPROXY('3JNmm1CUH9H9P6lVsx1y3W',#5,'Structure','',$,#36,#38,$,.ELEMENT.);
#40=IFCSITE('2PJ1ax1HL4SgHFFReEEwE$',#5,'Default Site','',$,$,$,$,.ELEMENT.,$,$,$,$,$);
#41=IFCRELAGGREGATES('1J6GQExT511x6QRu5FmkD2',#5,'ProjectLink','',#23,(#40));
#42=IFCBUILDING('1tIoXRzCXF3vuIMrF6RVcd',#5,'Default Building','',$,$,$,$,.ELEMENT.,$,$,$);
#43=IFCRELAGGREGATES('2GkPanCgnAzQY_0xv8dnHH',#5,'SiteLink','',#40,(#42));
#44=IFCBUILDINGSTOREY('1L8$GCIw116uw35vpyjSsO',#5,'Default Storey','',$,$,$,$,.ELEMENT.,$);
#45=IFCRELAGGREGATES('1lB$$h00nFaPQb2gvlhRX$',#5,'DefaultStoreyLink','',#42,(#44));
#46=IFCRELCONTAINEDINSPATIALSTRUCTURE('3dnpjDLD5DvuyUGcyHqRvU',#5,'UnassignedObjectsLink','',(#39),#44);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,54 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('col.ifc','2021-01-11T05:12:19',('',''),(''),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.19 build 23652 (Git)','FreeCAD','118df2cf_ed21_438e_a41');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,1610341939,#3,#4,1610341939);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.,0.));
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCDIRECTION((0.,1.));
#21=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#20);
#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#21,$,.MODEL_VIEW.,$);
#23=IFCPROJECT('2iAYrakL9FABNNwZfj$CbO',#5,'BIMTester Example 1 - IFC4',$,$,$,$,(#21),#19);
#24=IFCDIRECTION((1.,0.));
#25=IFCCARTESIANPOINT((0.,0.));
#26=IFCAXIS2PLACEMENT2D(#25,#24);
#27=IFCCIRCLEPROFILEDEF(.AREA.,$,#26,0.2);
#28=IFCCARTESIANPOINT((0.,0.,0.));
#29=IFCAXIS2PLACEMENT3D(#28,#7,#6);
#30=IFCEXTRUDEDAREASOLID(#27,#29,#7,5.);
#31=IFCCOLOURRGB($,1.,0.5,1.);
#32=IFCSURFACESTYLERENDERING(#31,$,$,$,$,$,$,$,.FLAT.);
#33=IFCSURFACESTYLE($,.BOTH.,(#32));
#34=IFCPRESENTATIONSTYLEASSIGNMENT((#33));
#35=IFCSTYLEDITEM(#30,(#34),$);
#36=IFCLOCALPLACEMENT($,#9);
#37=IFCSHAPEREPRESENTATION(#22,'Body','SweptSolid',(#30));
#38=IFCPRODUCTDEFINITIONSHAPE($,$,(#37));
#39=IFCBUILDINGELEMENTPROXY('3JNmm1CUH9H9P6lVsx1y3W',#5,'Structure','',$,#36,#38,$,.COMPLEX.);
#40=IFCSITE('2PJ1ax1HL4SgHFFReEEwE$',#5,'Default Site','',$,$,$,$,.ELEMENT.,$,$,$,$,$);
#41=IFCRELAGGREGATES('1J6GQExT511x6QRu5FmkD2',#5,'ProjectLink','',#23,(#40));
#42=IFCBUILDING('1tIoXRzCXF3vuIMrF6RVcd',#5,'Default Building','',$,$,$,$,.ELEMENT.,$,$,$);
#43=IFCRELAGGREGATES('2GkPanCgnAzQY_0xv8dnHH',#5,'SiteLink','',#40,(#42));
#44=IFCBUILDINGSTOREY('1L8$GCIw116uw35vpyjSsO',#5,'Default Storey','',$,$,$,$,.ELEMENT.,$);
#45=IFCRELAGGREGATES('1lB$$h00nFaPQb2gvlhRX$',#5,'DefaultStoreyLink','',#42,(#44));
#46=IFCRELCONTAINEDINSPATIALSTRUCTURE('3dnpjDLD5DvuyUGcyHqRvU',#5,'UnassignedObjectsLink','',(#39),#44);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,18 @@
# language: de
Funktionalität: Basisdaten
Um BIM-Daten anzusehen
Für alle beteiligten Akteure
Wir brauchen eine IFC-Datei
Szenario: Bereitstellen von IFC-Daten
* Die IFC-Datei wurde durch einen Startparameter zur Verfügung gestellt
* Die IFC-Daten müssen das IFC2X3 Schema benutzen
Szenario: Projektinformationen
* Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "BIMTester Example 1 - IFC2X3"
@@ -0,0 +1,18 @@
# language: en
Feature: Base setup
In order to view the BIM data
As any interested stakeholder
We need an IFC file
Scenario: Receiving a file
* The IFC file has been provided through an argument
* IFC data must use the IFC2X3 schema
Scenario: Project information
* The project name, code, or short identifier must be "BIMTester Example 1 - IFC2X3"
@@ -0,0 +1,18 @@
# language: fr
Fonctionnalité: Base setup
In order to view the BIM data
As any interested stakeholder
We need an IFC file
Scénario: Recevoir e fichier
* The IFC file has been provided through an argument
* Les données IFC doivent utiliser le schéma IFC2X3
Scénario: Project information
* The project name, code, or short identifier must be "BIMTester Example 1 - IFC2X3"
@@ -0,0 +1,18 @@
# language: it
Funzionalità: Dati di base
Per poter consultare dati BIM
a tutti gli attori partecipanti
serve un file IFC
Scenario: Preparare Dati IFC
* Il file IFC è stato fornito attraverso un argumento
* I dati IFC devono seguire lo schema IFC2X3
Scenario: Project information
* Il nome del progetto, codice o identificatore breve deve essere "BIMTester Example 1 - IFC2X3"
@@ -0,0 +1,18 @@
# language: nl
Functionaliteit: Basisgegevens
Om BIM-gegevens te bekijken
Zoals elke geïnteresseerde stakeholder
We hebben een IFC-bestand nodig
Scenario: Bestand ontvangen
* The IFC file has been provided through an argument
* IFC-gegevens moeten het IFC2X3 -schema gebruiken
Scenario: Project informatie
* De projectnaam, code of korte ID moet "BIMTester Example 1 - IFC2X3"
+91 -57
View File
@@ -1,13 +1,19 @@
#!/usr/bin/env python3
import argparse
import os
from bimtester import clean
from bimtester import reports
from bimtester import run
def show_widget(features="", ifcfile=""):
def show_widget(
features="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
import sys
from PySide2 import QtWidgets
@@ -18,7 +24,12 @@ def show_widget(features="", ifcfile=""):
app = QtWidgets.QApplication(sys.argv)
# Create and show the form
form = GuiWidgetBimTester(features, ifcfile)
form = GuiWidgetBimTester(
features,
ifcfile,
get_featurepath_from_ifcpath,
args
)
form.show()
# Run the main Qt loop
@@ -27,15 +38,76 @@ def show_widget(features="", ifcfile=""):
if __name__ == "__main__":
# TODO make similar to bash commands
# use - not _ in named args
parser = argparse.ArgumentParser(
description="Runs unit tests for BIM data"
)
parser.add_argument(
"-a",
"--advanced-arguments",
type=str,
help="Specify your own arguments to Python's Behave",
default=""
)
parser.add_argument(
"-c",
"--console",
action="store_true",
help="Show results in the console"
)
parser.add_argument(
"-d",
"--featuresdir",
type=str,
help=(
"Specify a features directory. This should contain "
"a directory named 'features' which contains all the "
"feature files."
),
default=""
)
parser.add_argument(
"-f",
"--feature",
type=str,
help="Specify a feature file to test",
default=""
)
parser.add_argument(
"-g",
"--gui",
action="store_true",
help=(
"Start the gui. The option t (copyintemprun) "
"is triggered automaticly."
)
)
parser.add_argument(
"-i",
"--ifcfile",
type=str,
help=(
"Specify a ifc file."
),
default=""
)
parser.add_argument(
"-p",
"--purge",
action="store_true",
help="Purge tests of deleted elements"
)
parser.add_argument(
"-path",
"--path",
type=str,
help=(
"Specify a path to prepend to feature and ifc file"
),
default=""
)
parser.add_argument(
"-r",
"--report",
@@ -49,66 +121,18 @@ if __name__ == "__main__":
help="Generate a HTML report after running the tests"
)
parser.add_argument(
"-path",
"--path",
type=str,
help=(
"Specify a path to prepend to feature and ifc file"
),
default=""
)
parser.add_argument(
"-c",
"--console",
action="store_true",
help="Show results in the console"
)
parser.add_argument(
"-f",
"--feature",
type=str,
help="Specify a feature file to test",
default=""
)
parser.add_argument(
"-a",
"--advanced-arguments",
type=str,
help="Specify your own arguments to Python's Behave",
default=""
)
parser.add_argument(
"-g",
"--gui",
"-t",
"--copyintemprun",
action="store_true",
help=(
"Start the gui. The option t (run in temp directory) "
"is triggered automaticly."
"Copy steps and feature files into a temporary directory "
"and run bimtester with them."
)
)
parser.add_argument(
"featuresdir",
type=str,
nargs="?",
help=(
"Specify a features directory. This should contain "
" a directory named features which contains all the "
"feature files."
),
default=""
)
parser.add_argument(
"ifcfile",
type=str,
nargs="?",
help=(
"Specify a ifc file."
),
default=""
)
args = vars(parser.parse_args())
print(args)
from json import dumps
print(dumps(args, indent=4))
if args["path"]:
if args["feature"]:
@@ -121,9 +145,19 @@ if __name__ == "__main__":
elif args["report"]:
reports.generate_report()
elif args["gui"]:
show_widget(args["featuresdir"], args["ifcfile"])
args["copyintemprun"] = True
fea = args["featuresdir"]
ifc = args["ifcfile"]
if fea != "" and ifc != "":
show_widget(fea, ifc, False, args)
elif fea == "" and ifc != "":
show_widget(fea, ifc, True, args)
elif args["copyintemprun"]:
run.run_tests(args)
# TODO merge with else, but do not forget the tmp dir is not known
else:
run.run_tests(args)
if args["report_after_run"]:
reports.generate_report()
print("# All tasks are complete :-)")
+1 -15
View File
@@ -266,21 +266,7 @@ endif
rm -rf dist/working
# Provides IFCPatch functionality
cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/ifcpatch.py
cd dist/blenderbim/libs/site/packages/ && mkdir recipes
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/__init__.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/ExtractElements.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/MergeProject.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/Migrate.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/OffsetObjectPlacements.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/OffsetStoreyElevations.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/Optimise.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/RecycleNonRootedElements.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/RemoveSiteRepresentation.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/ResetAbsoluteCoordinates.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/ResetSpatialElementLocations.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/SetRefElevation.py
cd dist/blenderbim/libs/site/packages/recipes && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcpatch/recipes/SplitByBuildingStorey.py
cd dist/blenderbim/libs/site/packages/ && svn export https://github.com/IfcOpenShell/IfcOpenShell/trunk/src/ifcpatch
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blender28-bim-$(VERSION)-$(PLATFORM).zip ./*
+45 -123
View File
@@ -5,46 +5,49 @@ bpy = sys.modules.get("bpy")
if bpy is not None:
import bpy
import blenderbim.bim.module.root as module_root
import blenderbim.bim.module.aggregate as module_aggregate
import blenderbim.bim.module.attribute as module_attribute
import blenderbim.bim.module.bcf as module_bcf
import blenderbim.bim.module.context as module_context
import blenderbim.bim.module.covetool as module_covetool
import blenderbim.bim.module.debug as module_debug
import blenderbim.bim.module.geometry as module_geometry
import blenderbim.bim.module.model as module_model
import blenderbim.bim.module.owner as module_owner
import blenderbim.bim.module.project as module_project
import blenderbim.bim.module.pset as module_pset
import blenderbim.bim.module.spatial as module_spatial
import blenderbim.bim.module.style as module_style
import blenderbim.bim.module.type as module_type
import blenderbim.bim.module.unit as module_unit
import importlib
from . import ui, prop, operator
modules = {
"root": None,
"aggregate": None,
"attribute": None,
"bcf": None,
"cobie": None,
"context": None,
"covetool": None,
"csv": None,
"diff": None,
"bimtester": None,
"debug": None,
"geometry": None,
"georeference": None,
"material": None,
"model": None,
"owner": None,
"project": None,
"pset": None,
"spatial": None,
"style": None,
"type": None,
"unit": None,
"void": None,
}
for name in modules.keys():
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
classes = [
operator.SelectClass,
operator.SelectType,
operator.OpenUri,
operator.SelectFeaturesDir,
operator.SelectDiffJsonFile,
operator.SelectDiffNewFile,
operator.SelectDiffOldFile,
operator.SelectDataDir,
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.ValidateIfcFile,
operator.ExportIFC,
operator.ImportIFC,
operator.ColourByClass,
operator.ColourByAttribute,
operator.ColourByPset,
operator.ResetObjectColours,
operator.ApproveClass,
operator.RejectClass,
operator.SelectAudited,
operator.RejectElement,
operator.SelectExternalMaterialDir,
operator.AddSweptSolid,
operator.RemoveSweptSolid,
@@ -54,8 +57,6 @@ if bpy is not None:
operator.SelectSweptSolidInnerCurves,
operator.AssignSweptSolidExtrusion,
operator.SelectSweptSolidExtrusion,
operator.AddQto,
operator.RemoveQto,
operator.AddMaterialPset,
operator.RemoveMaterialPset,
operator.AddMaterialLayer,
@@ -86,10 +87,6 @@ if bpy is not None:
operator.SelectGlobalId,
operator.SelectAttribute,
operator.SelectPset,
operator.CreateAggregate,
operator.EditAggregate,
operator.SaveAggregate,
operator.ExplodeAggregate,
operator.LoadClassification,
operator.AddClassification,
operator.RemoveClassification,
@@ -107,8 +104,6 @@ if bpy is not None:
operator.OpenView,
operator.OpenViewCamera,
operator.ActivateView,
operator.ExecuteIfcDiff,
operator.VisualiseDiff,
operator.ExportClashSets,
operator.ImportClashSets,
operator.AddClashSet,
@@ -133,11 +128,6 @@ if bpy is not None:
operator.RemovePropertyTemplate,
operator.AddSectionPlane,
operator.RemoveSectionPlane,
operator.AddCsvAttribute,
operator.RemoveCsvAttribute,
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.EyedropIfcCsv,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
@@ -148,14 +138,6 @@ if bpy is not None:
operator.AddVariable,
operator.RemoveVariable,
operator.PropagateTextData,
operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal,
operator.GuessQuantity,
operator.ExecuteBIMTester,
operator.BIMTesterPurge,
operator.SelectCobieIfcFile,
operator.SelectCobieJsonFile,
operator.ExecuteIfcCobie,
operator.SelectIfcPatchInput,
operator.SelectIfcPatchOutput,
operator.ExecuteIfcPatch,
@@ -178,8 +160,6 @@ if bpy is not None:
operator.BuildSchedule,
operator.AddScheduleToSheet,
operator.SetViewportShadowFromSun,
operator.SetNorthOffset,
operator.GetNorthOffset,
operator.AddPresentationLayer,
operator.AssignPresentationLayer,
operator.UnassignPresentationLayer,
@@ -197,17 +177,10 @@ if bpy is not None:
operator.LinkIfc,
operator.SnapSpacesTogether,
operator.CopyGrid,
operator.AddSectionsAnnotations,
prop.StrProperty,
prop.Attribute,
prop.MaterialLayer,
prop.MaterialConstituent,
prop.MaterialProfile,
prop.MaterialSet,
prop.Variable,
prop.Role,
prop.Address,
prop.Person,
prop.Organisation,
prop.Classification,
prop.ClassificationReference,
prop.ClassificationView,
@@ -227,8 +200,6 @@ if bpy is not None:
prop.BIMProperties,
prop.DocProperties,
prop.BIMLibrary,
prop.MapConversion,
prop.TargetCRS,
prop.IfcParameter,
prop.BoundaryCondition,
prop.PsetQto,
@@ -245,26 +216,17 @@ if bpy is not None:
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
ui.BIM_PT_sheets,
ui.BIM_PT_bim,
ui.BIM_PT_psets,
ui.BIM_PT_classifications,
ui.BIM_PT_document_information,
ui.BIM_PT_constraints,
ui.BIM_PT_search,
ui.BIM_PT_ifccsv,
ui.BIM_PT_ifcclash,
ui.BIM_PT_qa,
ui.BIM_PT_library,
ui.BIM_PT_gis,
ui.BIM_PT_presentation_layers,
ui.BIM_PT_diff,
ui.BIM_PT_cobie,
ui.BIM_PT_patch,
ui.BIM_PT_mvd,
ui.BIM_PT_material,
ui.BIM_PT_presentation_layer_data,
ui.BIM_PT_object_material,
ui.BIM_PT_object_qto,
ui.BIM_PT_classification_references,
ui.BIM_PT_documents,
ui.BIM_PT_constraint_relations,
@@ -288,22 +250,8 @@ if bpy is not None:
ui.BIM_ADDON_preferences,
]
classes.extend(module_root.classes)
classes.extend(module_aggregate.classes)
classes.extend(module_attribute.classes)
classes.extend(module_bcf.classes)
classes.extend(module_context.classes)
classes.extend(module_covetool.classes)
classes.extend(module_debug.classes)
classes.extend(module_geometry.classes)
classes.extend(module_model.classes)
classes.extend(module_owner.classes)
classes.extend(module_project.classes)
classes.extend(module_pset.classes)
classes.extend(module_spatial.classes)
classes.extend(module_style.classes)
classes.extend(module_type.classes)
classes.extend(module_unit.classes)
for module in modules.values():
classes.extend(module.classes)
def menu_func_export(self, context):
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
@@ -325,31 +273,18 @@ if bpy is not None:
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary)
bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion)
bpy.types.Scene.TargetCRS = bpy.props.PointerProperty(type=prop.TargetCRS)
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
module_root.register()
module_aggregate.register()
module_attribute.register()
module_bcf.register()
module_context.register()
module_covetool.register()
module_debug.register()
module_geometry.register()
module_model.register()
module_owner.register()
module_project.register()
module_pset.register()
module_spatial.register()
module_style.register()
module_type.register()
module_unit.register()
for module in modules.values():
module.register()
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
bpy.app.handlers.load_post.append(prop.toggleDecorationsOnLoad)
@@ -361,29 +296,16 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del bpy.types.Scene.BIMProperties
del bpy.types.Scene.DocProperties
del bpy.types.Scene.MapConversion
del bpy.types.Scene.TargetCRS
del bpy.types.Object.BIMObjectProperties
del bpy.types.Collection.BIMObjectProperties
del bpy.types.Material.BIMObjectProperties
del bpy.types.Collection.BIMObjectProperties # Check if we need this
del bpy.types.Material.BIMMaterialProperties
del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
module_unit.unregister()
module_type.unregister()
module_style.unregister()
module_spatial.unregister()
module_pset.unregister()
module_project.unregister()
module_owner.unregister()
module_model.unregister()
module_geometry.unregister()
module_debug.unregister()
module_covetool.unregister()
module_context.unregister()
module_bcf.unregister()
module_attribute.register()
module_aggregate.register()
module_root.unregister()
for module in reversed(list(modules.values())):
module.unregister()
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
@@ -13,6 +13,7 @@ import gpu
import bgl
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
from gpu_extras.batch import batch_for_shader
from . import helper
class BaseDecorator():
@@ -641,7 +642,7 @@ class LevelDecorator(BaseDecorator):
self.draw_labels(context, obj, splines)
class PlanDecorator(LevelDecorator):
class PlanLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Plan Level"
DEF_GLSL = BaseDecorator.DEF_GLSL + """
@@ -723,7 +724,7 @@ class PlanDecorator(LevelDecorator):
self.draw_label(context, text, p0, dir, gap=8, center=False)
class SectionDecorator(LevelDecorator):
class SectionLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Section Level"
DEF_GLSL = BaseDecorator.DEF_GLSL + """
@@ -996,6 +997,108 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
class SectionViewDecorator(LevelDecorator):
basename = "IfcAnnotation/Section"
DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
"""
GEOM_GLSL = """
uniform vec2 winsize;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 dir, in vec4 side, in float length, in float width, out vec4 head[3]) {
vec4 nose = dir * length;
vec4 ear = side * width;
head[0] = vec4(0);
head[1] = nose * .5 + ear;
head[2] = nose;
}
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(CIRCLE_SIZE, head);
vec4 head3[3];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + gap + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
p = p0w + head3[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
p = p1w + head3[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(context, obj, verts, [(0, 1)])
class DecorationsHandler():
decorators_classes = [
DimensionDecorator,
@@ -1004,10 +1107,11 @@ class DecorationsHandler():
HiddenDecorator,
LeaderDecorator,
MiscDecorator,
PlanDecorator,
SectionDecorator,
PlanLevelDecorator,
SectionLevelDecorator,
StairDecorator,
BreakDecorator
BreakDecorator,
SectionViewDecorator
]
installed = None
@@ -1031,11 +1135,9 @@ class DecorationsHandler():
self.decorators = [cls() for cls in self.decorators_classes]
def __call__(self, context):
props = context.scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
collection, _ = helper.get_active_drawing(context.scene)
if collection is None:
return
drawing = props.drawings[props.active_drawing_index]
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
for decorator in self.decorators:
for obj in decorator.get_objects(collection):
+45 -4
View File
@@ -299,6 +299,27 @@ def parse_diagram_scale(camera):
return float(numerator) / float(denominator)
def get_project_collection(scene):
"""Get main project collection"""
colls = [c for c in scene.collection.children if c.name.startswith('IfcProject')]
if len(colls) != 1:
raise RuntimeError("project collection missing or not unique")
return colls[0]
def get_active_drawing(scene):
"""Get active drawing collection and camera"""
props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
drawing = props.drawings[props.active_drawing_index]
return scene.collection.children['Views'].children[f"IfcGroup/{drawing.name}"], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
def ortho_view_frame(camera, margin=0.015):
"""Calculates 2d bounding box of camera view area.
@@ -308,7 +329,7 @@ def ortho_view_frame(camera, margin=0.015):
:type camera: bpy.types.Camera + BIMCameraProperties
:arg margin: margins, in scene units
:type margin: float
:return: (xmin, xmax, ymin, ymax) in local camera coordinates
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
"""
aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
size = camera.ortho_scale
@@ -317,7 +338,11 @@ def ortho_view_frame(camera, margin=0.015):
scale = parse_diagram_scale(camera)
xmarg = margin * scale
ymarg = margin * scale * aspect
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg)
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg, -camera.clip_start, -camera.clip_end)
def almost_zero(v):
return abs(v) < 1e-5
def clip_segment(bounds, segm):
@@ -329,11 +354,11 @@ def clip_segment(bounds, segm):
"""
# LiangBarsky algorithm
xmin, xmax, ymin, ymax = bounds
xmin, xmax, ymin, ymax, _, _ = bounds
p1, p2 = segm
def clip_side(p, q):
if abs(p) < 1e-10: # ~= 0, parallel to the side
if almost_zero(p): # ~= 0, parallel to the side
if q < 0:
return None # outside
else:
@@ -368,3 +393,19 @@ def clip_segment(bounds, segm):
p2c = p1 + dlt * t2
return p1c, p2c
def elevate_segment(bounds, segm):
"""Elevate line xy-perpendicular segment vertically
:arg bounds: (xmin, xmax, ymin, ymax)
:arg segm: 2 vertices of the segment
:return: 2 new vertices of segment or None if segment outside the bounding box
"""
_, _, ymin, ymax, zmin, _ = bounds
p1, p2 = segm
dlt = p2 - p1
if not (almost_zero(dlt.x) and almost_zero(dlt.y)):
return None
x = p1.x
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
+124 -417
View File
@@ -17,6 +17,7 @@ import math
import multiprocessing
import zipfile
import tempfile
import numpy as np
from pathlib import Path
from itertools import cycle
from datetime import datetime
@@ -85,7 +86,7 @@ class MaterialCreator:
item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add()
item_id.name = str(item.id())
styled_item = item.StyledByItem[0] # Cardinality is S[0:1]
styled_item = item.StyledByItem[0] # Cardinality is S[0:1]
style_name = self.get_surface_style_name(styled_item)
if not style_name:
@@ -130,7 +131,9 @@ class MaterialCreator:
material_to_slot[i] = slot_index
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
material_index = [(material_to_slot[mat_id] if mat_id != -1 else 0) for mat_id in self.mesh["ios_material_ids"]]
material_index = [
(material_to_slot[mat_id] if mat_id != -1 else 0) for mat_id in self.mesh["ios_material_ids"]
]
self.mesh.polygons.foreach_set("material_index", material_index)
def canonicalise_material_name(self, name):
@@ -180,86 +183,31 @@ class MaterialCreator:
def create_single(self, material):
if material.Name not in self.materials:
self.create_new_single(material)
self.obj.BIMObjectProperties.material_type = "IfcMaterial"
self.obj.BIMObjectProperties.material = self.materials[material.Name]
def create_layer_set(self, layer_set):
props = self.obj.BIMObjectProperties
props.material_type = "IfcMaterialLayerSet"
props.material_set.name = layer_set.LayerSetName or ""
if hasattr(layer_set, "Description"): # IFC2X3 support
props.material_set.description = layer_set.Description or ""
for layer in layer_set.MaterialLayers:
new = props.material_set.material_layers.add()
if layer.Material:
if layer.Material.Name not in self.materials:
self.create_new_single(layer.Material)
new.material = self.materials[layer.Material.Name]
new.layer_thickness = layer.LayerThickness
new.is_ventilated = "TRUE" if layer.IsVentilated else "FALSE"
if not hasattr(layer, "Name"):
continue # IFC2X3 support
new.name = layer.Name or ""
new.description = layer.Description or ""
try:
new.category = layer.Category if layer.Category else "None"
except:
new.custom_category = layer.Category or ""
new.priority = layer.Priority or 0
def create_constituent_set(self, constituent_set):
props = self.obj.BIMObjectProperties
props.material_type = "IfcMaterialConstituentSet"
props.material_set.name = constituent_set.Name or ""
props.material_set.description = constituent_set.Description or ""
for constituent in constituent_set.MaterialConstituents:
new = props.material_set.material_constituents.add()
new.name = constituent.Name or ""
new.description = constituent.Description or ""
if constituent.Material.Name not in self.materials:
self.create_new_single(constituent.Material)
new.material = self.materials[constituent.Material.Name]
new.fraction = constituent.Fraction or 0.0
new.category = constituent.Category or ""
def create_profile_set(self, profile_set):
props = self.obj.BIMObjectProperties
props.material_type = "IfcMaterialProfileSet"
props.material_set.name = profile_set.Name or ""
props.material_set.description = profile_set.Description or ""
for profile in profile_set.MaterialProfiles:
new = props.material_set.material_profiles.add()
new.name = profile.Name or ""
new.description = profile.Description or ""
if profile.Material.Name not in self.materials:
self.create_new_single(profile.Material)
new.material = self.materials[profile.Material.Name]
try:
new.profile = profile.Profile.is_a()
for i, attribute in enumerate(profile.Profile):
newa = new.profile_attributes.add()
newa.name = profile.Profile.attribute_name(i)
newa.string_value = str(attribute)
except:
pass # TODO: currently, only parametric profile sets are supported
new.priority = profile.Priority or 0
new.category = profile.Category or ""
def create_material_list(self, material_list):
props = self.obj.BIMObjectProperties
props.material_type = "IfcMaterialConstituentSet" # Constituent sets are the recommended upgrade path
for material in material_list.Materials:
new = props.material_set.material_constituents.add()
if material.Name not in self.materials:
self.create_new_single(material)
new.material = self.materials[material.Name]
def create_new_single(self, material):
self.materials[material.Name] = obj = bpy.data.materials.new(material.Name)
obj.BIMMaterialProperties.ifc_definition_id = int(material.id())
self.ifc_importer.add_element_attributes(material, obj.BIMMaterialProperties)
for pset in getattr(material, "HasProperties", ()):
self.ifc_importer.add_pset(pset, obj.BIMMaterialProperties)
obj.BIMObjectProperties.ifc_definition_id = int(material.id())
if not material.HasRepresentation or not material.HasRepresentation[0].Representations:
return
for representation in material.HasRepresentation[0].Representations:
@@ -280,7 +228,7 @@ class MaterialCreator:
if style.Name:
return style.Name
return str(style.id())
return None # We only support surface styles right now
return None # We only support surface styles right now
def parse_styled_item(self, styled_item, material):
styles = self.get_styled_item_styles(styled_item)
@@ -288,7 +236,6 @@ class MaterialCreator:
if not style.is_a("IfcSurfaceStyle"):
continue
material.BIMMaterialProperties.ifc_style_id = int(style.id())
external_style = None
for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0
@@ -301,13 +248,6 @@ class MaterialCreator:
surface_style.SurfaceColour.Blue,
alpha,
)
elif surface_style.is_a("IfcExternallyDefinedSurfaceStyle"):
external_style = surface_style
if external_style:
material.BIMMaterialProperties.is_external = True
material.BIMMaterialProperties.location = external_style.Location
material.BIMMaterialProperties.identification = external_style.Identification
material.BIMMaterialProperties.name = external_style.Name
# IfcPresentationStyleAssignment is deprecated as of IFC4
# However it is still widely used thanks to Revit :(
@@ -395,8 +335,6 @@ class IfcImporter:
self.profile_code("Purge diffs")
self.load_existing_rooted_elements()
self.profile_code("Load existing rooted elements")
self.cache_file()
self.profile_code("Caching file")
self.load_file()
self.profile_code("Loading file")
self.set_ifc_file()
@@ -406,8 +344,8 @@ class IfcImporter:
self.profile_code("Set vendor worksarounds")
self.calculate_unit_scale()
self.profile_code("Calculate unit scale")
self.patch_ifc()
self.profile_code("Patching ifc")
self.calculate_model_offset()
self.profile_code("Calculate model offset")
self.set_units()
self.profile_code("Set units")
self.create_project()
@@ -434,8 +372,6 @@ class IfcImporter:
if self.ifc_import_settings.should_import_native:
self.parse_native_elements()
self.profile_code("Parsing native elements")
self.create_georeferencing()
self.profile_code("Georeferencing ifc")
self.create_groups()
self.profile_code("Creating groups")
self.create_grids()
@@ -443,12 +379,8 @@ class IfcImporter:
if self.ifc_import_settings.should_import_native:
self.create_native_products()
self.profile_code("Creating native products")
# TODO: Deprecate after bug #682 is fixed and the new importer is stable
if self.ifc_import_settings.should_use_legacy:
self.create_products_legacy()
else:
self.create_products()
self.profile_code("Creating meshified products")
self.create_products()
self.profile_code("Creating meshified products")
self.relate_openings()
self.profile_code("Relating openings")
self.place_objects_in_spatial_tree()
@@ -471,46 +403,22 @@ class IfcImporter:
self.profile_code("Create presentation layers")
self.add_project_to_scene()
self.profile_code("Add project to scene")
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 10000:
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 1000:
self.clean_mesh()
self.profile_code("Mesh cleaning")
def auto_set_workarounds(self):
if (
"DDS-CAD" in self.file.wrapped_data.header.file_name.originating_system
or "DDS" in self.file.wrapped_data.header.file_name.preprocessor_version
):
self.ifc_import_settings.should_reset_absolute_coordinates = True
applications = self.file.by_type("IfcApplication")
if not applications:
return
if applications[0].ApplicationIdentifier == "Revit":
if self.is_ifc_class_far_away("IfcSite"):
self.ifc_import_settings.should_ignore_site_coordinates = True
self.ifc_import_settings.should_guess_georeferencing = True
if self.is_ifc_class_far_away("IfcBuilding"):
self.ifc_import_settings.should_ignore_building_coordinates = True
self.ifc_import_settings.should_guess_georeferencing = True
elif "prostructures" in applications[0].ApplicationFullName.lower():
if "prostructures" in applications[0].ApplicationFullName.lower():
self.ifc_import_settings.should_allow_non_element_aggregates = True
elif applications[0].ApplicationFullName.lower() == "12d model":
self.ifc_import_settings.should_reset_absolute_coordinates = True
elif "Civil 3D" in applications[0].ApplicationFullName:
self.ifc_import_settings.should_reset_absolute_coordinates = True
elif applications[0].ApplicationFullName == "Tekla Structures":
if self.is_ifc_class_far_away("IfcSite"):
self.ifc_import_settings.should_ignore_site_coordinates = True
def is_ifc_class_far_away(self, ifc_class):
for site in self.file.by_type(ifc_class):
if (
not site.ObjectPlacement
or not site.ObjectPlacement.RelativePlacement
or not site.ObjectPlacement.RelativePlacement.Location
):
continue
if self.is_point_far_away(site.ObjectPlacement.RelativePlacement.Location):
return True
def is_element_far_away(self, element):
try:
return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location)
except:
pass
def is_point_far_away(self, point):
# Arbitrary threshold based on experience
@@ -600,94 +508,94 @@ class IfcImporter:
products.extend(self.get_products_from_shape_representation(inverse_element))
return products
def patch_ifc(self):
def calculate_model_offset(self):
project = self.file.by_type("IfcProject")[0]
if self.ifc_import_settings.should_ignore_site_coordinates:
sites = self.find_decomposed_ifc_class(project, "IfcSite")
if self.ifc_import_settings.should_guess_georeferencing and sites:
self.guess_georeferencing(sites[0])
for site in sites:
self.patch_placement_to_origin(site)
if self.ifc_import_settings.should_ignore_building_coordinates:
buildings = self.find_decomposed_ifc_class(project, "IfcBuilding")
if self.ifc_import_settings.should_guess_georeferencing and buildings:
self.guess_georeferencing(buildings[0])
for building in buildings:
self.patch_placement_to_origin(building)
if self.ifc_import_settings.should_reset_absolute_coordinates:
self.reset_absolute_coordinates()
site = self.find_decomposed_ifc_class(project, "IfcSite")
if site and self.is_element_far_away(site[0]):
return self.guess_georeferencing(site[0])
building = self.find_decomposed_ifc_class(project, "IfcBuilding")
if building and self.is_element_far_away(building[0]):
return self.guess_georeferencing(building[0])
return self.guess_absolute_coordinate()
def guess_georeferencing(self, element):
if not element.ObjectPlacement.is_a("IfcLocalPlacement"):
return
placement = element.ObjectPlacement.RelativePlacement
bpy.context.scene.MapConversion.eastings = str(placement.Location.Coordinates[0])
bpy.context.scene.MapConversion.northings = str(placement.Location.Coordinates[1])
bpy.context.scene.MapConversion.orthogonal_height = str(placement.Location.Coordinates[2])
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(placement.Location.Coordinates[0])
props.blender_northings = str(placement.Location.Coordinates[1])
props.blender_orthogonal_height = str(placement.Location.Coordinates[2])
if placement.RefDirection:
bpy.context.scene.MapConversion.x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
bpy.context.scene.MapConversion.x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1] * -1)
bpy.context.scene.MapConversion.scale = "1"
props.blender_x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
props.blender_x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1])
props.has_blender_offset = True
props.blender_offset_type = "OBJECT_PLACEMENT"
def reset_absolute_coordinates(self):
# 12D can have some funky coordinates out of any sensible range. This
# method will not work all the time, but will catch most issues.
def guess_absolute_coordinate(self):
# Civil BIM applications like to work in absolute coordinates, where the ObjectPlacement is 0,0,0 but each
# individual coordinate of the shape representation is in absolute values.
offset_point = self.get_offset_point()
if not offset_point:
return
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(offset_point[0])
props.blender_northings = str(offset_point[1])
props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True
props.blender_offset_type = "CARTESIAN_POINT"
def get_offset_point(self):
offset_point = None
elements_checked = 0
# If more than these points aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 100
try:
point_lists = self.file.by_type("IfcCartesianPointList3D")
except:
# IFC2X3 does not have IfcCartesianPointList3D
point_lists = []
for point_list in point_lists:
coord_list = [None] * len(point_list.CoordList)
elements_checked += 1
if elements_checked > element_checking_threshold:
return
for i, point in enumerate(point_list.CoordList):
if len(point) == 2 or not self.is_point_far_away(point):
coord_list[i] = point
continue
if not offset_point:
offset_point = (point[0], point[1], point[2])
self.ifc_import_settings.logger.info("Resetting absolute coordinates by %s", point)
point = (point[0] - offset_point[0], point[1] - offset_point[1], point[2] - offset_point[2])
coord_list[i] = point
point_list.CoordList = coord_list
if len(point) == 3 and self.is_point_far_away(point):
return point[0]
# offset_point = (point[0], point[1], point[2])
for point in self.file.by_type("IfcCartesianPoint"):
if len(point.Coordinates) == 2 or not self.is_point_far_away(point):
continue
if not offset_point:
offset_point = (point.Coordinates[0], point.Coordinates[1], point.Coordinates[2])
self.ifc_import_settings.logger.info("Resetting absolute coordinates by %s", point)
point.Coordinates = (
point.Coordinates[0] - offset_point[0],
point.Coordinates[1] - offset_point[1],
point.Coordinates[2] - offset_point[2],
elements_checked += 1
if elements_checked > element_checking_threshold:
return
if len(point.Coordinates) == 3 and self.is_point_far_away(point):
return point[0]
def apply_blender_offset_to_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
test = mathutils.Matrix(
ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
).tolist()
)
if not offset_point:
return
if self.file.wrapped_data.schema == "IFC2X3":
properties = [
self.file.createIfcPropertySingleValue(
"Eastings", None, self.file.createIfcLengthMeasure(offset_point[0])
),
self.file.createIfcPropertySingleValue(
"Northings", None, self.file.createIfcLengthMeasure(offset_point[1])
),
self.file.createIfcPropertySingleValue(
"OrthogonalHeight", None, self.file.createIfcLengthMeasure(offset_point[2])
),
]
history = self.file.createIfcOwnerHistory()
pset = self.file.createIfcPropertySet(
ifcopenshell.guid.new(), history, "EPset_MapConversion", None, properties
return mathutils.Matrix(
ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
).tolist()
)
self.file.createIfcRelDefinesByProperties(
ifcopenshell.guid.new(), history, None, None, self.file.by_type("IfcSite"), pset
)
else:
# We don't have the full geolocation information, so we'll add what we can
scene = bpy.context.scene
scene.MapConversion.eastings = str(offset_point[0])
scene.MapConversion.northings = str(offset_point[1])
scene.MapConversion.orthogonal_height = str(offset_point[2])
return mathutils.Matrix(matrix.tolist())
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
@@ -701,60 +609,6 @@ class IfcImporter:
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
def patch_placement_to_origin(self, element):
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, 0.0)
if element.ObjectPlacement.RelativePlacement.Axis:
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0)
if element.ObjectPlacement.RelativePlacement.RefDirection:
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
def create_georeferencing(self):
try:
map_conversion = self.file.by_type("IfcMapConversion")
projected_crs = self.file.by_type("IfcProjectedCRS")
if not map_conversion or not projected_crs:
return
except:
return # For example, in IFC2X3
map_conversion = map_conversion[0]
projected_crs = projected_crs[0]
scene = bpy.context.scene
scene.BIMProperties.has_georeferencing = True
map_conversion_map = {
"Eastings": "eastings",
"Northings": "northings",
"OrthogonalHeight": "orthogonal_height",
"XAxisAbscissa": "x_axis_abscissa",
"XAxisOrdinate": "x_axis_ordinate",
"Scale": "scale",
}
target_crs_map = {
"Name": "name",
"Description": "description",
"GeodeticDatum": "geodetic_datum",
"VerticalDatum": "vertical_datum",
"MapProjection": "map_projection",
"MapZone": "map_zone",
"MapUnit": "map_unit",
}
for keyA, keyB in map_conversion_map.items():
value = getattr(map_conversion, keyA)
if value is not None:
setattr(scene.MapConversion, keyB, str(value))
for keyA, keyB in target_crs_map.items():
value = getattr(projected_crs, keyA)
if value is not None:
if keyA == "MapUnit":
value = self.get_unit_name(value)
setattr(scene.TargetCRS, keyB, str(value))
def get_unit_name(self, named_unit):
name = ""
if hasattr(named_unit, "Prefix") and named_unit.Prefix:
name += named_unit.Prefix
name += named_unit.Name
return name
def create_groups(self):
group_collection = None
for collection in self.project["blender"].children:
@@ -773,7 +627,6 @@ class IfcImporter:
else:
obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None)
obj.BIMObjectProperties.ifc_definition_id = element.id()
self.add_element_attributes(element, obj.BIMObjectProperties)
group_collection.objects.link(obj)
self.groups[element.GlobalId] = {"ifc": element, "blender": obj}
@@ -811,7 +664,6 @@ class IfcImporter:
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
obj.matrix_world = matrix_world
self.add_element_attributes(axis, obj.BIMObjectProperties)
grid.objects.link(obj)
def create_type_products(self):
@@ -863,11 +715,6 @@ class IfcImporter:
):
return representation_map
def create_products_legacy(self):
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
for element in elements:
self.create_product_legacy(element)
def create_native_products(self):
if not self.native_elements:
return
@@ -962,20 +809,19 @@ class IfcImporter:
if shape:
m = shape.transformation.matrix.data
mat = mathutils.Matrix(
([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1])
# We use numpy here because Blender mathutils.Matrix is not accurate enough
mat = np.matrix(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
mat.transpose()
obj.matrix_world = mat
obj.matrix_world = self.apply_blender_offset_to_matrix(mat)
self.material_creator.create(element, obj, mesh)
elif hasattr(element, "ObjectPlacement"):
obj.matrix_world = self.get_element_matrix(element)
obj.matrix_world = self.apply_blender_offset_to_matrix(self.get_element_matrix(element))
self.add_element_representation_items(element, obj)
self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj)
self.add_opening_relation(element, obj)
self.add_product_definitions(element, obj)
self.added_data[element.GlobalId] = obj
if element.is_a("IfcOpeningElement"):
@@ -1336,7 +1182,7 @@ class IfcImporter:
obj = self.added_data[product.GlobalId]
obj.data.BIMMeshProperties.presentation_layer_index = layer_index
except:
pass # Occurs for example in opening elements or exclusions
pass # Occurs for example in opening elements or exclusions
def clean_mesh(self):
obj = None
@@ -1355,64 +1201,6 @@ class IfcImporter:
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
def add_product_definitions(self, element, obj):
if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy:
return
for definition in element.IsDefinedBy:
if not definition.is_a("IfcRelDefinesByProperties"):
continue
if definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
self.add_qto(definition.RelatingPropertyDefinition, obj)
def add_pset(self, pset, props):
new_pset = props.psets.add()
new_pset.name = pset.Name
pset_template = schema.ifc.psetqto.get_by_name(new_pset.name)
if pset_template:
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = new_pset.properties.add()
prop.name = prop_name
try:
if hasattr(pset, "HasProperties"):
props = pset.HasProperties
elif hasattr(pset, "Properties"):
props = pset.Properties
except:
return # I've seen ArchiCAD produce invalid IFCs with empty data
# Invalid IFC, but some vendors like Solidworks do this so we accomodate it
if not props:
return
for prop in props:
if prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
index = new_pset.properties.find(prop.Name)
if index >= 0:
new_pset.properties[index].string_value = str(prop.NominalValue.wrappedValue)
else:
new_prop = new_pset.properties.add()
new_prop.name = prop.Name
new_prop.string_value = str(prop.NominalValue.wrappedValue)
def add_qto(self, qto, obj):
new_qto = obj.BIMObjectProperties.qtos.add()
new_qto.name = str(qto.Name)
qto_template = schema.ifc.psetqto.get_by_name(new_qto.name)
if qto_template:
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = new_qto.properties.add()
prop.name = prop_name
for prop in qto.Quantities:
if prop.is_a("IfcPhysicalSimpleQuantity"):
value = getattr(prop, "{}Value".format(prop.is_a()[len("IfcQuantity") :]))
if not value:
continue
index = new_qto.properties.find(prop.Name)
if index >= 0:
new_qto.properties[index].string_value = str(value)
else:
new_prop = new_qto.properties.add()
new_prop.name = prop.Name
new_prop.string_value = str(value)
def add_opening_relation(self, element, obj):
if not element.is_a("IfcOpeningElement"):
return
@@ -1429,15 +1217,6 @@ class IfcImporter:
with open(self.ifc_import_settings.diff_file, "r") as file:
self.diff = json.load(file)
def cache_file(self):
destination = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", "ifc")
copythread = FileCopy(self.ifc_import_settings.input_file, destination)
bpy.context.scene.BIMProperties.ifc_cache = os.path.join(
destination, os.path.basename(self.ifc_import_settings.input_file)
)
copythread.start()
copythread.join()
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
extension = self.ifc_import_settings.input_file.split(".")[-1]
@@ -1708,7 +1487,6 @@ class IfcImporter:
self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj)
self.add_product_definitions(element, obj)
self.aggregates[element.GlobalId] = obj
self.aggregate_collections[rel_aggregate.id()] = collection
@@ -1731,47 +1509,6 @@ class IfcImporter:
objects_to_purge.append(obj)
bpy.ops.object.delete({"selected_objects": objects_to_purge})
def create_product_legacy(self, element):
if (
self.diff
and element.GlobalId not in self.diff["added"]
and element.GlobalId not in self.diff["changed"].keys()
):
return
self.ifc_import_settings.logger.info("Creating object %s", element)
self.time = time.time()
if element.is_a("IfcOpeningElement"):
return
try:
representation_id = self.get_representation_id(element)
mesh_name = "mesh-{}".format(representation_id)
mesh = self.meshes.get(mesh_name)
if mesh is None or representation_id is None:
shape = ifcopenshell.geom.create_shape(self.settings, element)
self.ifc_import_settings.logger.info("Shape was generated in %.2f", time.time() - self.time)
self.time = time.time()
mesh = self.create_mesh(element, shape)
self.meshes[mesh_name] = mesh
self.mesh_shapes[mesh_name] = shape
else:
self.ifc_import_settings.logger.info("Mesh reused.")
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
return
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
self.material_creator.create(element, obj, mesh)
obj.matrix_world = self.get_element_matrix(element, mesh_name)
self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj)
self.add_product_definitions(element, obj)
self.added_data[element.GlobalId] = obj
def add_element_document_relations(self, element, obj):
for association in element.HasAssociations:
if association.is_a("IfcRelAssociatesDocument"):
@@ -1812,7 +1549,7 @@ class IfcImporter:
return self.place_object_in_spatial_tree(container, obj)
elif element.is_a("IfcGrid"):
grid_collection = bpy.data.collections.get(obj.name)
if grid_collection: # Just in case we ran into invalid grids from Revit
if grid_collection: # Just in case we ran into invalid grids from Revit
self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
grid_collection.objects.link(obj)
else:
@@ -1852,21 +1589,6 @@ class IfcImporter:
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
bpy.context.scene.collection.objects.link(obj)
def add_element_attributes(self, element, props):
attributes = element.get_info()
for key, value in attributes.items():
if (
value is None
or isinstance(value, (tuple, ifcopenshell.entity_instance))
or key == "id"
or key == "type"
):
continue
attribute = props.attributes.add()
attribute.name = key
attribute.data_type = "string"
attribute.string_value = str(self.cast_edge_case_attribute(element.is_a(), key, value))
def cast_edge_case_attribute(self, ifc_class, key, value):
if key == "RefLatitude" or key == "RefLongitude":
return ifcopenshell.util.geolocation.dms2dd(*value)
@@ -1904,38 +1626,11 @@ class IfcImporter:
return self.get_referenced_source_name(element.ReferencedSource)
def get_element_matrix(self, element, mesh_name=None):
element_matrix = self.get_local_placement(element.ObjectPlacement)
if mesh_name:
# Blender supports reusing a mesh with a different transformation
# applied at the object level. In contrast, IFC supports reusing a mesh
# with a different transformation applied at the mesh level _as well as_
# the object level. For this reason, if the end-goal is to re-use mesh
# data, we must combine IFC's mesh-level transformation into Blender's
# object level transformation.
# The first step to do this is to _undo_ the mesh-level transformation
# from whatever shared mesh we are using, as it is not necessarily the
# same as the current mesh.
shared_shape_transformation = self.get_representation_cartesian_transformation(
self.file.by_id(self.mesh_shapes[mesh_name].product.id())
)
if shared_shape_transformation:
shared_transform = self.get_cartesiantransformationoperator(shared_shape_transformation)
shared_transform.invert()
element_matrix = element_matrix @ shared_transform
# The next step is to apply the current element's mesh level
# transformation to our current element's object transformation
transformation = self.get_representation_cartesian_transformation(element)
if transformation:
element_matrix = self.get_cartesiantransformationoperator(transformation) @ element_matrix
element_matrix[0][3] *= self.unit_scale
element_matrix[1][3] *= self.unit_scale
element_matrix[2][3] *= self.unit_scale
return element_matrix
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
result[1][3] *= self.unit_scale
result[2][3] *= self.unit_scale
return result
def get_body_representations(self, representations, matrix=None):
if matrix is None:
@@ -2005,11 +1700,29 @@ class IfcImporter:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
mesh = bpy.data.meshes.new("{}/{}".format(
self.file.by_id(representation_id).ContextOfItems.id(), geometry.id))
mesh = bpy.data.meshes.new(
"{}/{}".format(self.file.by_id(representation_id).ContextOfItems.id(), geometry.id)
)
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
ordinate_index = 0
verts = [None] * len(geometry.verts)
offset_point = (
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
)
for i, vert in enumerate(geometry.verts):
if ordinate_index > 2:
ordinate_index = 0
verts[i] = vert - offset_point[ordinate_index]
ordinate_index += 1
else:
verts = geometry.verts
if geometry.faces:
num_vertices = len(geometry.verts) // 3
num_vertices = len(verts) // 3
total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
@@ -2021,11 +1734,11 @@ class IfcImporter:
# Potentially, there is a smarter way to do this. See #1047
v_index = cycle((0, 1, 2))
verts = [
v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in geometry.verts
v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in verts
]
mesh.vertices.foreach_set("co", verts)
else:
mesh.vertices.foreach_set("co", geometry.verts)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
@@ -2034,7 +1747,7 @@ class IfcImporter:
mesh.update()
else:
e = geometry.edges
v = geometry.verts
v = verts
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, [])
@@ -2051,6 +1764,7 @@ class IfcImporter:
except:
self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
import traceback
print(traceback.format_exc())
def create_curve(self, geometry):
@@ -2155,7 +1869,6 @@ class IfcImportSettings:
self.should_auto_set_workarounds = True
self.should_ignore_site_coordinates = False
self.should_ignore_building_coordinates = False
self.should_reset_absolute_coordinates = False
self.should_merge_materials_by_colour = False
self.should_import_type_representations = False
self.should_import_curves = False
@@ -2164,7 +1877,6 @@ class IfcImportSettings:
self.should_use_cpu_multiprocessing = False
self.should_import_with_profiling = False
self.should_import_native = False
self.should_use_legacy = False
self.should_merge_aggregates = False
self.should_merge_by_class = False
self.should_merge_by_material = False
@@ -2181,10 +1893,6 @@ class IfcImportSettings:
settings.diff_file = scene_bim.diff_json_file
settings.ifc_import_filter = scene_bim.ifc_import_filter
settings.ifc_selector = scene_bim.ifc_selector
settings.should_ignore_site_coordinates = scene_bim.import_should_ignore_site_coordinates
settings.should_ignore_building_coordinates = scene_bim.import_should_ignore_building_coordinates
settings.should_reset_absolute_coordinates = scene_bim.import_should_reset_absolute_coordinates
settings.should_guess_georeferencing = scene_bim.import_should_guess_georeferencing
settings.should_import_type_representations = scene_bim.import_should_import_type_representations
settings.should_import_curves = scene_bim.import_should_import_curves
settings.should_import_opening_elements = scene_bim.import_should_import_opening_elements
@@ -2194,7 +1902,6 @@ class IfcImportSettings:
settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling
settings.should_import_native = scene_bim.import_should_import_native
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.should_use_legacy = scene_bim.import_should_use_legacy
settings.should_import_aggregates = scene_bim.import_should_import_aggregates
settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates
settings.should_merge_by_class = scene_bim.import_should_merge_by_class
@@ -0,0 +1,18 @@
import bpy
from . import ui, operator
classes = (
operator.AssignObject,
operator.EnableEditingAggregate,
operator.DisableEditingAggregate,
operator.AddAggregate,
ui.BIM_PT_aggregate,
)
def register():
pass
def unregister():
pass
@@ -0,0 +1,49 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"relating_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
decomposes = None
if self.settings["product"].Decomposes:
decomposes = self.settings["product"].Decomposes[0]
is_decomposed_by = None
for rel in self.settings["relating_object"].IsDecomposedBy:
if rel.is_a("IfcRelAggregates"):
is_decomposed_by = rel
break
if decomposes and decomposes == is_decomposed_by:
return
if decomposes:
related_objects = list(decomposes.RelatedObjects)
related_objects.remove(self.settings["product"])
if related_objects:
decomposes.RelatedObjects = related_objects
else:
self.file.remove(decomposes)
if is_decomposed_by:
related_objects = list(is_decomposed_by.RelatedObjects)
related_objects.append(self.settings["product"])
is_decomposed_by.RelatedObjects = related_objects
else:
is_decomposed_by = self.file.create_entity(
"IfcRelAggregates",
**{
"GlobalId": ifcopenshell.guid.new(),
# TODO "OwnerHistory": None
"RelatedObjects": [self.settings["product"]],
"RelatingObject": self.settings["relating_object"],
}
)
@@ -0,0 +1,17 @@
from blenderbim.bim.ifc import IfcStore
class Data:
products = {}
@classmethod
def load(cls, product_id):
file = IfcStore.get_file()
if not file:
return
product = file.by_id(product_id)
if product.Decomposes and product.Decomposes[0].is_a("IfcRelAggregates"):
obj = product.Decomposes[0].RelatingObject
cls.products[product_id] = {"type": obj.is_a(), "Name": obj.Name, "id": int(obj.id())}
else:
cls.products[product_id] = {"type": None, "Name": None, "id": None}
@@ -0,0 +1,88 @@
import bpy
import blenderbim.bim.module.aggregate.assign_object as assign_object
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.aggregate.data import Data
class AssignObject(bpy.types.Operator):
bl_idname = "bim.assign_object"
bl_label = "Assign Object"
relating_object: bpy.props.StringProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
props = related_object.BIMObjectProperties
relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
product = self.file.by_id(props.ifc_definition_id)
assign_object.Usecase(
self.file,
{
"product": product,
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
},
).execute()
bpy.ops.bim.edit_object_placement(obj=related_object.name)
Data.load(props.ifc_definition_id)
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
self.remove_collection(bpy.context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
collection.children.link(spatial_collection)
continue
self.remove_collection(collection, spatial_collection)
else:
for collection in related_object.users_collection:
collection.objects.unlink(related_object)
relating_collection.objects.link(related_object)
return {"FINISHED"}
def remove_collection(self, parent, child):
try:
parent.children.unlink(child)
except:
pass
class EnableEditingAggregate(bpy.types.Operator):
bl_idname = "bim.enable_editing_aggregate"
bl_label = "Enable Editing Aggregate"
def execute(self, context):
bpy.context.active_object.BIMObjectProperties.relating_object = None
bpy.context.active_object.BIMObjectProperties.is_editing_aggregate = True
return {"FINISHED"}
class DisableEditingAggregate(bpy.types.Operator):
bl_idname = "bim.disable_editing_aggregate"
bl_label = "Disable Editing Aggregate"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj.BIMObjectProperties.is_editing_aggregate = False
return {"FINISHED"}
class AddAggregate(bpy.types.Operator):
bl_idname = "bim.add_aggregate"
bl_label = "Add Aggregate"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
bpy.context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=aggregate.name)
return {"FINISHED"}
@@ -0,0 +1,44 @@
from bpy.types import Panel
from blenderbim.bim.module.aggregate.data import Data
class BIM_PT_aggregate(Panel):
bl_label = "IFC Aggregation"
bl_idname = "BIM_PT_aggregate"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if props.ifc_definition_id not in Data.products:
Data.load(props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
return False
return True
def draw(self, context):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return
if props.ifc_definition_id not in Data.products:
Data.load(props.ifc_definition_id)
if props.is_editing_aggregate:
row = self.layout.row(align=True)
row.prop(props, "relating_object", text="")
row.operator("bim.assign_object", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_aggregate", icon="X", text="")
else:
row = self.layout.row(align=True)
name = "{}/{}".format(
Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"]
)
if name == "None/None":
name = "This object is not part of an aggregation"
row.label(text=name)
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
@@ -0,0 +1,21 @@
import bpy
from . import ui, prop, operator
classes = (
operator.EnableEditingAttributes,
operator.DisableEditingAttributes,
operator.EditAttributes,
prop.BIMAttributeProperties,
ui.BIM_PT_object_attributes,
ui.BIM_PT_material_attributes,
)
def register():
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
bpy.types.Material.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
def unregister():
del bpy.types.Object.BIMAttributeProperties
del bpy.types.Material.BIMAttributeProperties
@@ -0,0 +1,56 @@
import ifcopenshell
from blenderbim.bim.ifc import IfcStore
class Data:
products = {}
@classmethod
def load(cls, product_id):
file = IfcStore.get_file()
if not file:
return
product = file.by_id(product_id)
cls.products[product_id] = []
declaration = IfcStore.get_schema().declaration_by_name(product.is_a())
for attribute in declaration.all_attributes():
data_type = str(attribute.type_of_attribute())
value = getattr(product, attribute.name())
list_type = None
enum_items = ()
if "<entity" in data_type:
data_type = "entity"
value = None if value is None else str(value)
elif "<list" in data_type:
if "<entity" in data_type:
list_type = "entity"
elif "<string>" in data_type:
list_type = "string"
elif "<real>" in data_type:
list_type = "float"
elif "<integer>" in data_type:
list_type = "integer"
data_type = "list"
value = None if value is None else str(value)
elif "<string>" in data_type:
data_type = "string"
value = None if value is None else str(value)
elif "<real>" in data_type:
data_type = "float"
value = None if value is None else float(value)
elif "<integer>" in data_type:
data_type = "integer"
value = None if value is None else int(value)
elif "<enumeration" in data_type:
data_type = "enum"
value = None if value is None else str(value)
enum_items = attribute.type_of_attribute().declared_type().enumeration_items()
cls.products[product_id].append({
"name": attribute.name(),
"value": value,
"type": data_type,
"enum_items": enum_items,
"list_type": list_type,
"is_optional": attribute.optional(),
"is_null": getattr(product, attribute.name()) is None
})
@@ -0,0 +1,13 @@
class Usecase():
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"attributes": {}
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["product"], name, value)
@@ -0,0 +1,102 @@
import bpy
import json
import blenderbim.bim.module.attribute.edit_attributes as edit_attributes
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.attribute.data import Data
class EnableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.enable_editing_attributes"
bl_label = "Enable Editing Attributes"
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
while len(props.attributes) > 0:
props.attributes.remove(0)
for attribute in Data.products[oprops.ifc_definition_id]:
new = props.attributes.add()
if attribute["type"] == "entity":
continue
new.name = attribute["name"]
new.is_null = attribute["is_null"]
if attribute["type"] == "string" or attribute["type"] == "list":
new.string_value = attribute["value"] or ""
elif attribute["type"] == "integer":
new.int_value = attribute["value"] or 0
elif attribute["type"] == "float":
new.float_value = attribute["value"] or 0.
elif attribute["type"] == "enum":
new.enum_items = json.dumps(attribute["enum_items"])
if attribute["value"]:
new.enum_value = attribute["value"]
props.is_editing_attributes = True
return {"FINISHED"}
class DisableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.disable_editing_attributes"
bl_label = "Disable Editing Attributes"
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
props = obj.BIMAttributeProperties
props.is_editing_attributes = False
return {"FINISHED"}
class EditAttributes(bpy.types.Operator):
bl_idname = "bim.edit_attributes"
bl_label = "Edit Attributes"
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
attributes = {}
for attribute in Data.products[oprops.ifc_definition_id]:
blender_attribute = props.attributes.get(attribute["name"])
if not blender_attribute:
continue
if attribute["is_optional"] and blender_attribute.is_null:
attributes[attribute["name"]] = None
elif attribute["type"] == "string":
attributes[attribute["name"]] = blender_attribute.string_value
elif attribute["type"] == "list":
values = blender_attribute.string_value[1:-1].split(", ")
if attribute["list_type"] == "float":
values = [float(v) for v in values]
elif attribute["list_type"] == "integer":
values = [int(v) for v in values]
attributes[attribute["name"]] = values
elif attribute["type"] == "integer":
attributes[attribute["name"]] = blender_attribute.int_value
elif attribute["type"] == "float":
attributes[attribute["name"]] = blender_attribute.float_value
elif attribute["type"] == "enum":
attributes[attribute["name"]] = blender_attribute.enum_value
edit_attributes.Usecase(self.file, {
"product": self.file.by_id(oprops.ifc_definition_id),
"attributes": attributes
}).execute()
Data.load(oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_attributes(obj=self.obj, obj_type=self.obj_type)
return {"FINISHED"}
@@ -0,0 +1,20 @@
import bpy
import blenderbim.bim.schema # refactor
from blenderbim.bim.module.material.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMAttributeProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
@@ -0,0 +1,95 @@
from bpy.types import Panel
from blenderbim.bim.module.attribute.data import Data
def draw_ui(context, layout, obj_type):
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
if oprops.ifc_definition_id not in Data.products:
Data.load(oprops.ifc_definition_id)
if props.is_editing_attributes:
row = layout.row(align=True)
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
op.obj_type = obj_type
op.obj = obj.name
op = row.operator("bim.disable_editing_attributes", icon="X", text="")
op.obj_type = obj_type
op.obj = obj.name
for attribute in Data.products[oprops.ifc_definition_id]:
if attribute["type"] == "entity":
continue
row = layout.row(align=True)
blender_attribute = props.attributes.get(attribute["name"])
if attribute["type"] == "string" or attribute["type"] == "list":
row.prop(blender_attribute, "string_value", text=attribute["name"])
elif attribute["type"] == "integer":
row.prop(blender_attribute, "int_value", text=attribute["name"])
elif attribute["type"] == "float":
row.prop(blender_attribute, "float_value", text=attribute["name"])
elif attribute["type"] == "enum":
row.prop(blender_attribute, "enum_value", text=attribute["name"])
if attribute["name"] == "GlobalId":
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
if attribute["is_optional"]:
row.prop(
blender_attribute,
"is_null",
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
text="",
)
# TODO: reimplement, see #1222
# op = row.operator("bim.copy_attribute_to_selection", icon="COPYDOWN", text="")
# op.attribute_name = attribute.name
# op.attribute_value = attribute.string_value
else:
row = layout.row()
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
op.obj_type = obj_type
op.obj = obj.name
for attribute in Data.products[oprops.ifc_definition_id]:
if attribute["value"] is None or attribute["type"] == "entity":
continue
row = layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=str(attribute["value"]))
# TODO: reimplement, see #1222
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
# self.draw_addresses_ui()
class BIM_PT_object_attributes(Panel):
bl_label = "IFC Attributes"
bl_idname = "BIM_PT_object_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
draw_ui(context, self.layout, "Object")
class BIM_PT_material_attributes(Panel):
bl_label = "IFC Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
@classmethod
def poll(cls, context):
try:
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
except:
return False
def draw(self, context):
draw_ui(context, self.layout, "Material")
@@ -0,0 +1,25 @@
import bpy
from . import ui, prop, operator
classes = (
operator.ExecuteBIMTester,
operator.BIMTesterPurge,
operator.SelectFeaturesDir,
operator.RejectElement,
operator.ColourByClass,
operator.ResetObjectColours,
operator.ApproveClass,
operator.RejectClass,
operator.SelectAudited,
prop.BimTesterProperties,
ui.BIM_PT_qa,
)
def register():
bpy.types.Scene.BimTesterProperties = bpy.props.PointerProperty(type=prop.BimTesterProperties)
def unregister():
del bpy.types.Scene.BimTesterProperties
@@ -0,0 +1,205 @@
import bpy
import ifcopenshell
import bimtester
import os
import webbrowser
from pathlib import Path
from itertools import cycle
class ExecuteBIMTester(bpy.types.Operator):
bl_idname = "bim.execute_bim_tester"
bl_label = "Execute BIMTester"
def execute(self, context):
filename = os.path.join(
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
)
cwd = os.getcwd()
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
bimtester.run.run_tests({"feature": filename, "advanced_arguments": None, "console": False})
bimtester.reports.generate_report()
webbrowser.open(
"file://"
+ os.path.join(
bpy.context.scene.BimTesterProperties.features_dir,
"report",
bpy.context.scene.BimTesterProperties.features_file + ".feature.html",
)
)
os.chdir(cwd)
return {"FINISHED"}
class BIMTesterPurge(bpy.types.Operator):
bl_idname = "bim.bim_tester_purge"
bl_label = "Purge Tests"
def execute(self, context):
filename = os.path.join(
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
)
cwd = os.getcwd()
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
bimtester.clean.TestPurger().purge()
os.chdir(cwd)
return {"FINISHED"}
class SelectFeaturesDir(bpy.types.Operator):
bl_idname = "bim.select_features_dir"
bl_label = "Select Features Directory"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BimTesterProperties.features_dir = (
os.path.dirname(os.path.abspath(self.filepath)) if "." in self.filepath else self.filepath
)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class RejectElement(bpy.types.Operator):
bl_idname = "bim.reject_element"
bl_label = "Reject Element"
def execute(self, context):
lines = []
for object in bpy.context.selected_objects:
lines.append(
" * The element {} should not exist because {}".format(
object.BIMObjectProperties.attributes[
object.BIMObjectProperties.attributes.find("GlobalId")
].string_value,
bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
)
)
QAHelper.append_to_scenario(lines)
return {"FINISHED"}
class ColourByClass(bpy.types.Operator):
bl_idname = "bim.colour_by_class"
bl_label = "Colour by Class"
def execute(self, context):
colours = cycle(colour_list)
ifc_classes = {}
for obj in bpy.context.visible_objects:
if "/" not in obj.name:
continue
ifc_class = obj.name.split("/")[0]
if ifc_class not in ifc_classes:
ifc_classes[ifc_class] = next(colours)
obj.color = ifc_classes[ifc_class]
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
class ResetObjectColours(bpy.types.Operator):
bl_idname = "bim.reset_object_colours"
bl_label = "Reset Colours"
def execute(self, context):
for object in bpy.context.selected_objects:
object.color = (1, 1, 1, 1)
return {"FINISHED"}
class ApproveClass(bpy.types.Operator):
bl_idname = "bim.approve_class"
bl_label = "Approve Class"
def execute(self, context):
lines = []
for object in bpy.context.selected_objects:
index = object.BIMObjectProperties.attributes.find("GlobalId")
if index != -1:
lines.append(
" * The element {} is an {}".format(
object.BIMObjectProperties.attributes[index].string_value, object.name.split("/")[0]
)
)
QAHelper.append_to_scenario(lines)
return {"FINISHED"}
class RejectClass(bpy.types.Operator):
bl_idname = "bim.reject_class"
bl_label = "Reject Class"
def execute(self, context):
lines = []
for object in bpy.context.selected_objects:
lines.append(
" * The element {} is an {}".format(
object.BIMObjectProperties.attributes[
object.BIMObjectProperties.attributes.find("GlobalId")
].string_value,
bpy.context.scene.BimTesterProperties.audit_ifc_class,
)
)
QAHelper.append_to_scenario(lines)
return {"FINISHED"}
class SelectAudited(bpy.types.Operator):
bl_idname = "bim.select_audited"
bl_label = "Select Audited"
def execute(self, context):
audited_global_ids = []
for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
with open(filename, "r") as feature_file:
lines = feature_file.readlines()
for line in lines:
words = line.strip().split()
for word in words:
if self.is_a_global_id(word):
audited_global_ids.append(word)
for object in bpy.context.visible_objects:
index = object.BIMObjectProperties.attributes.find("GlobalId")
if index != -1 and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids:
object.select_set(True)
return {"FINISHED"}
def is_a_global_id(self, word):
return word[0] in ["0", "1", "2", "3"] and len(word) == 22
class QAHelper:
@classmethod
def append_to_scenario(cls, lines):
filename = os.path.join(
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
)
if os.path.exists(filename + "~"):
os.remove(filename + "~")
os.rename(filename, filename + "~")
with open(filename, "w") as destination:
with open(filename + "~", "r") as source:
is_in_scenario = False
for source_line in source:
if (
"Scenario: " in source_line
and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
):
is_in_scenario = True
elif is_in_scenario:
for line in lines:
destination.write(line + "\n")
is_in_scenario = False
destination.write(source_line)
os.remove(filename + "~")
colour_list = [
(0.651, 0.81, 0.892, 1),
(0.121, 0.471, 0.706, 1),
(0.699, 0.876, 0.54, 1),
(0.199, 0.629, 0.174, 1),
(0.983, 0.605, 0.602, 1),
(0.89, 0.101, 0.112, 1),
(0.989, 0.751, 0.427, 1),
(0.986, 0.497, 0.1, 1),
(0.792, 0.699, 0.839, 1),
(0.414, 0.239, 0.603, 1),
(0.993, 0.999, 0.6, 1),
(0.693, 0.349, 0.157, 1),
]
@@ -0,0 +1,89 @@
import os
from pathlib import Path
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
scenarios_enum = []
featuresfiles_enum = []
classes_enum = []
def getIfcClasses(self, context): # This is a copy of the one in bim.prop (as it is used in other modules, can be refactored later)
global classes_enum
file = IfcStore.get_file()
if len(classes_enum) < 1 and file:
declaration = IfcStore.get_schema().declaration_by_name(self.ifc_product)
def get_classes(declaration):
results = []
if not declaration.is_abstract():
results.append(declaration.name())
for subtype in declaration.subtypes():
results.extend(get_classes(subtype))
return results
classes = get_classes(declaration)
classes_enum.extend([(c, c, "") for c in sorted(classes)])
return classes_enum
def getFeaturesFiles(self, context):
global featuresfiles_enum
if len(featuresfiles_enum) < 1:
featuresfiles_enum.clear()
for filename in Path(context.scene.BimTesterProperties.features_dir).glob("*.feature"):
f = str(filename.stem)
featuresfiles_enum.append((f, f, ""))
return featuresfiles_enum
def refreshFeaturesFiles(self, context):
global featuresfiles_enum
featuresfiles_enum.clear()
getFeaturesFiles(self, context)
def getScenarios(self, context):
global scenarios_enum
if len(scenarios_enum) < 1:
scenarios_enum.clear()
if context.scene.BimTesterProperties.features_file != '': # To handle the error when no .feature file exists in the folder
filename = os.path.join(
context.scene.BimTesterProperties.features_dir, context.scene.BimTesterProperties.features_file + ".feature"
)
with open(filename, "r") as feature_file:
lines = feature_file.readlines()
for line in lines:
if "Scenario:" in line:
s = line.strip()[len("Scenario: ") :]
scenarios_enum.append((s, s, ""))
return scenarios_enum
def refreshScenarios(self, context):
global scenarios_enum
scenarios_enum.clear()
getScenarios(self, context)
class BimTesterProperties(PropertyGroup):
features_dir: StringProperty(default="", name="Features Directory", update=refreshFeaturesFiles)
features_file: EnumProperty(items=getFeaturesFiles, name="Features File", update=refreshScenarios)
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
scenario: EnumProperty(items=getScenarios, name="Scenario")
# should_load_from_memory: BoolProperty(default=False, name="Load from Memory") # can be added later to mimic the functionality in the CSV Module
@@ -0,0 +1,61 @@
import bpy
from bpy.types import Panel
class BIM_PT_qa(Panel):
bl_label = "BIMTester Quality Auditing"
bl_idname = "BIM_PT_qa"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
bimtester_properties = bpy.context.scene.BimTesterProperties
layout.label(text="Gherkin Setup:")
row = layout.row(align=True)
row.prop(bimtester_properties, "features_dir")
row.operator("bim.select_features_dir", icon="FILE_FOLDER", text="")
if bimtester_properties.features_dir:
row = layout.row(align=True)
row.prop(bimtester_properties, "features_file")
row = layout.row(align=True)
row.prop(bimtester_properties, "scenario")
else:
return
if str(context.scene.BimTesterProperties.features_file) != '': # To handle the error when no .feature file exists in the folder
row = layout.row()
row.operator("bim.execute_bim_tester")
row = layout.row()
row.operator("bim.bim_tester_purge")
layout.label(text="Quality Auditing:")
row = layout.row()
row.prop(bimtester_properties, "qa_reject_element_reason")
row = layout.row()
row.operator("bim.reject_element")
row = layout.row(align=True)
row.operator("bim.colour_by_class")
row.operator("bim.reset_object_colours")
row = layout.row()
row.prop(bimtester_properties, "audit_ifc_class")
row = layout.row(align=True)
row.operator("bim.approve_class")
row.operator("bim.reject_class")
row = layout.row()
row.operator("bim.select_audited")
@@ -0,0 +1,18 @@
import bpy
from . import ui, prop, operator
classes = (
operator.SelectCobieIfcFile,
operator.SelectCobieJsonFile,
operator.ExecuteIfcCobie,
prop.COBieProperties,
ui.BIM_PT_cobie,
)
def register():
bpy.types.Scene.COBieProperties = bpy.props.PointerProperty(type=prop.COBieProperties)
def unregister():
del bpy.types.Scene.COBieProperties
@@ -0,0 +1,99 @@
import bpy
import os
import logging
import ifcopenshell
import json
import webbrowser
import tempfile
from blenderbim.bim.ifc import IfcStore
class SelectCobieIfcFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_ifc_file"
bl_label = "Select COBie IFC File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.COBieProperties.cobie_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectCobieJsonFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_json_file"
bl_label = "Select COBie JSON File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.COBieProperties.cobie_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcCobie(bpy.types.Operator):
bl_idname = "bim.execute_ifc_cobie"
bl_label = "Execute IFCCOBie"
file_format: bpy.props.StringProperty()
def execute(self, context):
from cobie import IfcCobieParser
props = bpy.context.scene.COBieProperties
output_dir = os.path.dirname(props.cobie_ifc_file)
if props.should_load_from_memory:
output_dir = tempfile.gettempdir()
output = os.path.join(output_dir, "output")
logger = logging.getLogger("IFCtoCOBie")
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s"))
logger = logging.getLogger("IFCtoCOBie")
logger.addHandler(fh)
selector = ifcopenshell.util.selector.Selector()
if props.cobie_json_file:
with open(props.cobie_json_file, "r") as f:
custom_data = json.load(f)
else:
custom_data = {}
parser = IfcCobieParser(logger, selector)
ifc_file = IfcStore.get_file()
if not (ifc_file and props.should_load_from_memory):
ifc_file = props.cobie_ifc_file
parser.parse(
ifc_file,
props.cobie_types,
props.cobie_components,
custom_data,
)
if self.file_format == "xlsx":
from cobie import CobieXlsWriter
writer = CobieXlsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
elif self.file_format == "ods":
from cobie import CobieOdsWriter
writer = CobieOdsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
else:
from cobie import CobieCsvWriter
writer = CobieCsvWriter(parser, output_dir)
writer.write()
webbrowser.open("file://" + output_dir)
webbrowser.open("file://" + output_dir + "/cobie.log")
return {"FINISHED"}
@@ -0,0 +1,21 @@
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class COBieProperties(PropertyGroup):
cobie_ifc_file: StringProperty(default="", name="COBie IFC File")
cobie_types: StringProperty(default=".COBieType", name="COBie Types")
cobie_components: StringProperty(default=".COBie", name="COBie Components")
cobie_json_file: StringProperty(default="", name="COBie JSON File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -0,0 +1,45 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_cobie(Panel):
bl_label = "IFC COBie"
bl_idname = "BIM_PT_cobie"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.COBieProperties
if IfcStore.get_file():
row = layout.row()
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "cobie_ifc_file")
row.operator("bim.select_cobie_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "cobie_types")
row = layout.row()
row.prop(props, "cobie_components")
row = layout.row(align=True)
row.prop(props, "cobie_json_file")
row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="")
row = layout.row()
op = row.operator("bim.execute_ifc_cobie", text="CSV")
op.file_format = "csv"
op = row.operator("bim.execute_ifc_cobie", text="ODS")
op.file_format = "ods"
op = row.operator("bim.execute_ifc_cobie", text="XLSX")
op.file_format = "xlsx"
@@ -0,0 +1,16 @@
import bpy
from . import ui, operator
classes = (
operator.AddSubcontext,
operator.RemoveSubcontext,
ui.BIM_PT_context,
)
def register():
pass
def unregister():
pass
@@ -0,0 +1,45 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"context": None,
"subcontext": None,
"target_view": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
parent = [
c
for c in self.file.by_type("IfcGeometricRepresentationContext")
if c.ContextType == self.settings["context"]
]
if not parent:
self.create_origin()
if self.settings["context"] == "Plan":
context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin)
else:
context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
project = self.file.by_type("IfcProject")[0]
if project.RepresentationContexts:
contexts = list(project.RepresentationContexts)
else:
contexts = []
contexts.append(context)
project.RepresentationContexts = contexts
return context
parent = parent[0]
return self.file.create_entity("IfcGeometricRepresentationSubContext", **{
"ContextIdentifier": self.settings["subcontext"],
"ContextType": self.settings["context"],
"ParentContext": parent,
"TargetView": self.settings["target_view"],
})
def create_origin(self):
self.origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)),
)
@@ -0,0 +1,29 @@
from blenderbim.bim.ifc import IfcStore
class Data:
is_loaded = False
contexts = {}
@classmethod
def load(cls):
file = IfcStore.get_file()
if not file:
return
cls.contexts = {}
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
subcontexts = {}
# See bug #1224 for why we don't use HasSubContexts
for subcontext in file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ParentContext != context:
continue
subcontexts[int(subcontext.id())] = {
"ContextType": subcontext.ContextType,
"ContextIdentifier": subcontext.ContextIdentifier,
"TargetView": subcontext.TargetView,
}
cls.contexts[int(context.id())] = {
"ContextType": context.ContextType,
"HasSubContexts": subcontexts
}
cls.is_loaded = True
@@ -0,0 +1,39 @@
import bpy
import blenderbim.bim.module.context.add_context as add_context
import blenderbim.bim.module.context.remove_context as remove_context
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.context.data import Data
class AddSubcontext(bpy.types.Operator):
bl_idname = "bim.add_subcontext"
bl_label = "Add Subcontext"
context: bpy.props.StringProperty()
subcontext: bpy.props.StringProperty()
target_view: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
add_context.Usecase(
self.file,
{
"context": self.context or bpy.context.scene.BIMProperties.available_contexts,
"subcontext": self.subcontext or bpy.context.scene.BIMProperties.available_subcontexts,
"target_view": self.target_view or bpy.context.scene.BIMProperties.available_target_views,
},
).execute()
Data.load()
return {"FINISHED"}
class RemoveSubcontext(bpy.types.Operator):
bl_idname = "bim.remove_subcontext"
bl_label = "Remove Context"
ifc_definition_id: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
usecase = remove_context.Usecase(self.file, {"context": self.file.by_id(self.ifc_definition_id)})
usecase.execute()
Data.load()
return {"FINISHED"}
@@ -0,0 +1,12 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"context": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
# TODO: this is a light remove only
for subcontext in self.settings["context"].HasSubContexts:
self.file.remove(subcontext)
self.file.remove(self.settings["context"])
@@ -0,0 +1,40 @@
from bpy.types import Panel
from blenderbim.bim.module.context.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_context(Panel):
bl_label = "IFC Geometric Representation Contexts"
bl_idname = "BIM_PT_context"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load()
props = context.scene.BIMProperties
row = self.layout.row(align=True)
row.prop(props, "available_contexts", text="")
row.prop(props, "available_subcontexts", text="")
row.prop(props, "available_target_views", text="")
row.operator("bim.add_subcontext", icon="ADD", text="")
for ifc_definition_id, context in Data.contexts.items():
box = self.layout.box()
row = box.row(align=True)
row.label(text=context["ContextType"])
row.operator("bim.remove_subcontext", icon="X", text="").ifc_definition_id = ifc_definition_id
for ifc_definition_id2, subcontext in context["HasSubContexts"].items():
row = box.row(align=True)
row.label(text=subcontext["ContextType"])
row.label(text=subcontext["ContextIdentifier"])
row.label(text=subcontext["TargetView"])
row.operator("bim.remove_subcontext", icon="X", text="").ifc_definition_id = ifc_definition_id2
@@ -0,0 +1,22 @@
import bpy
from . import ui, prop, operator
classes = (
operator.AddCsvAttribute,
operator.RemoveCsvAttribute,
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.EyedropIfcCsv,
prop.CsvProperties,
ui.BIM_PT_ifccsv,
)
def register():
bpy.types.Scene.CsvProperties = bpy.props.PointerProperty(type=prop.CsvProperties)
def unregister():
del bpy.types.Scene.CsvProperties
@@ -0,0 +1,91 @@
import bpy
import ifccsv
import ifcopenshell
import os
import logging
import json
import webbrowser
import tempfile
from blenderbim.bim.ifc import IfcStore
class AddCsvAttribute(bpy.types.Operator):
bl_idname = "bim.add_csv_attribute"
bl_label = "Add CSV Attribute"
def execute(self, context):
attribute = bpy.context.scene.CsvProperties.csv_attributes.add()
return {"FINISHED"}
class RemoveCsvAttribute(bpy.types.Operator):
bl_idname = "bim.remove_csv_attribute"
bl_label = "Remove CSV Attribute"
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.CsvProperties.csv_attributes.remove(self.index)
return {"FINISHED"}
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC to CSV"
filename_ext = ".csv"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
import ifccsv
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
ifc_file = ifcopenshell.open(bpy.context.scene.CsvProperties.csv_ifc_file)
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, bpy.context.scene.CsvProperties.ifc_selector)
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
ifc_csv.attributes = [a.name for a in bpy.context.scene.CsvProperties.csv_attributes]
ifc_csv.selector = selector
if bpy.context.scene.CsvProperties.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = bpy.context.scene.CsvProperties.csv_custom_delimiter
else:
ifc_csv.delimiter = bpy.context.scene.CsvProperties.csv_delimiter
ifc_csv.export(ifc_file, results)
return {"FINISHED"}
class ImportIfcCsv(bpy.types.Operator):
bl_idname = "bim.import_ifccsv"
bl_label = "Import CSV to IFC"
filename_ext = ".csv"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
import ifccsv
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
ifc_csv.Import(bpy.context.scene.CsvProperties.csv_ifc_file)
return {"FINISHED"}
class EyedropIfcCsv(bpy.types.Operator):
bl_idname = "bim.eyedrop_ifccsv"
bl_label = "Query Selected Items"
def execute(self, context):
global_ids = []
for obj in bpy.context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.attributes.get("GlobalId"):
global_ids.append("#" + obj.BIMObjectProperties.attributes.get("GlobalId").string_value)
bpy.context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
return {"FINISHED"}
@@ -0,0 +1,27 @@
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class CsvProperties(PropertyGroup):
csv_ifc_file: StringProperty(default="", name="CSV IFC File")
ifc_selector: StringProperty(default="", name="IFC Selector")
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
csv_delimiter: EnumProperty(
items=[(";", ";", ""), (",", ",", ""), (".", ".", ""), ("CUSTOM", "Custom", ""),],
name="IFC CSV Delimiter",
default=",",
)
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -0,0 +1,50 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_ifccsv(Panel):
bl_label = "IFC CSV Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.CsvProperties
if IfcStore.get_file():
row = layout.row()
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "csv_ifc_file")
row.operator("bim.import_ifccsv", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "ifc_selector")
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
row = layout.row()
row.label(text="Add IFC attributes to filter", icon="FILE_BLANK")
row.operator("bim.add_csv_attribute")
for index, attribute in enumerate(props.csv_attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
row = layout.row(align=True)
row.prop(props, "csv_delimiter")
if(props.csv_delimiter == 'CUSTOM'):
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row(align=True)
row.operator("bim.export_ifccsv", icon="EXPORT")
row.operator("bim.import_ifccsv", icon="IMPORT")
@@ -0,0 +1,21 @@
import bpy
from . import ui, prop, operator
classes = (
operator.ProfileImportIFC,
operator.CreateShapeFromStepId,
operator.SelectHighPolygonMeshes,
operator.InspectFromStepId,
operator.InspectFromObject,
operator.RewindInspector,
prop.BIMDebugProperties,
ui.BIM_PT_debug,
)
def register():
bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties)
def unregister():
del bpy.types.Scene.BIMDebugProperties
@@ -0,0 +1,133 @@
import bpy
import logging
import ifcopenshell
import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.ifc import IfcStore
class ProfileImportIFC(bpy.types.Operator):
bl_idname = "bim.profile_import_ifc"
bl_label = "Profile Import IFC"
def execute(self, context):
import cProfile
import pstats
cProfile.run(
f"import bpy; bpy.ops.import_ifc.bim(filepath='{bpy.context.scene.BIMProperties.ifc_file}')", "blender.prof"
)
p = pstats.Stats("blender.prof")
p.sort_stats("cumulative").print_stats(50)
return {"FINISHED"}
class CreateShapeFromStepId(bpy.types.Operator):
bl_idname = "bim.create_shape_from_step_id"
bl_label = "Create Shape From STEP ID"
def execute(self, context):
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
self.file = IfcStore.get_file()
element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
settings = ifcopenshell.geom.settings()
# settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
obj = bpy.data.objects.new("Debug", mesh)
bpy.context.scene.collection.objects.link(obj)
return {"FINISHED"}
class SelectHighPolygonMeshes(bpy.types.Operator):
bl_idname = "bim.select_high_polygon_meshes"
bl_label = "Select High Polygon Meshes"
def execute(self, context):
results = {}
for obj in bpy.data.objects:
if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int(
bpy.context.scene.BIMDebugProperties.number_of_polygons
):
continue
try:
obj.select_set(True)
except:
# If it is not in the view layer
pass
relating_type = obj.BIMObjectProperties.relating_type
if relating_type:
relating_type.select_set(True)
return {"FINISHED"}
class RewindInspector(bpy.types.Operator):
bl_idname = "bim.rewind_inspector"
bl_label = "Rewind Inspector"
def execute(self, context):
props = bpy.context.scene.BIMDebugProperties
total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2:
return {"FINISHED"}
previous_step_id = int(props.step_id_breadcrumb[total_breadcrumbs - 2].name)
props.step_id_breadcrumb.remove(total_breadcrumbs - 1)
props.step_id_breadcrumb.remove(total_breadcrumbs - 2)
bpy.ops.bim.inspect_from_step_id(step_id=previous_step_id)
return {"FINISHED"}
class InspectFromStepId(bpy.types.Operator):
bl_idname = "bim.inspect_from_step_id"
bl_label = "Inspect From STEP ID"
step_id: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
bpy.context.scene.BIMDebugProperties.active_step_id = self.step_id
crumb = bpy.context.scene.BIMDebugProperties.step_id_breadcrumb.add()
crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id)
while len(bpy.context.scene.BIMDebugProperties.attributes) > 0:
bpy.context.scene.BIMDebugProperties.attributes.remove(0)
while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
for key, value in element.get_info().items():
self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
for key in dir(element):
if (
not key[0].isalpha()
or key[0] != key[0].upper()
or key in element.get_info()
or not getattr(element, key)
):
continue
self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
return {"FINISHED"}
def add_attribute(self, prop, key, value):
if isinstance(value, tuple) and len(value) < 10:
for i, item in enumerate(value):
self.add_attribute(prop, key + f"[{i}]", item)
return
elif isinstance(value, tuple) and len(value) >= 10:
key = key + "({})".format(len(value))
new = prop.add()
new.name = key
new.string_value = str(value)
if isinstance(value, ifcopenshell.entity_instance):
new.int_value = int(value.id())
class InspectFromObject(bpy.types.Operator):
bl_idname = "bim.inspect_from_object"
bl_label = "Inspect From Object"
def execute(self, context):
ifc_definition_id = bpy.context.active_object.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
return {"FINISHED"}
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
return {"FINISHED"}
@@ -0,0 +1,21 @@
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMDebugProperties(PropertyGroup):
step_id: IntProperty(name="STEP ID")
number_of_polygons: IntProperty(name="Number of Polygons")
active_step_id: IntProperty(name="STEP ID")
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
attributes: CollectionProperty(name="Attributes", type=Attribute)
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
@@ -0,0 +1,64 @@
import bpy
from bpy.types import Panel
class BIM_PT_debug(Panel):
bl_label = "IFC Debug"
bl_idname = "BIM_PT_debug"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.BIMDebugProperties
row = layout.row()
row.operator("bim.profile_import_ifc")
row = layout.row()
row.prop(props, "step_id", text="")
row = layout.row()
row.operator("bim.create_shape_from_step_id")
row = layout.row()
row.prop(props, "number_of_polygons", text="")
row = layout.row()
row.operator("bim.select_high_polygon_meshes")
layout.label(text="Inspector:")
row = layout.row(align=True)
if len(props.step_id_breadcrumb) >= 2:
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="")
row = layout.row(align=True)
row.operator("bim.inspect_from_step_id").step_id = bpy.context.scene.BIMDebugProperties.active_step_id
row.operator("bim.inspect_from_object")
if props.attributes:
layout.label(text="Direct attributes:")
for index, attribute in enumerate(props.attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.prop(attribute, "string_value", text="")
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
).step_id = attribute.int_value
if props.inverse_attributes:
layout.label(text="Inverse attributes:")
for index, attribute in enumerate(props.inverse_attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.prop(attribute, "string_value", text="")
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
).step_id = attribute.int_value
@@ -0,0 +1,22 @@
import bpy
from . import ui, prop, operator
classes = (
operator.SelectDiffJsonFile,
operator.VisualiseDiff,
operator.SelectDiffOldFile,
operator.SelectDiffNewFile,
operator.ExecuteIfcDiff,
prop.DiffProperties,
ui.BIM_PT_diff,
)
def register():
bpy.types.Scene.DiffProperties = bpy.props.PointerProperty(type=prop.DiffProperties)
def unregister():
del bpy.types.Scene.DiffProperties
@@ -0,0 +1,101 @@
import bpy
import ifccsv
import ifcopenshell
import json
from blenderbim.bim.ifc import IfcStore
class SelectDiffJsonFile(bpy.types.Operator):
bl_idname = "bim.select_diff_json_file"
bl_label = "Select Diff JSON File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class VisualiseDiff(bpy.types.Operator):
bl_idname = "bim.visualise_diff"
bl_label = "Visualise Diff"
def execute(self, context):
#ifc_file = IfcStore.get_file() # In case we get from Store
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
with open(bpy.context.scene.DiffProperties.diff_json_file, "r") as file:
diff = json.load(file)
for obj in bpy.context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
#global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
continue
if global_id.string_value in diff["deleted"]:
obj.color = (1.0, 0.0, 0.0, 0.2)
elif global_id.string_value in diff["added"]:
obj.color = (0.0, 1.0, 0.0, 0.2)
elif global_id.string_value in diff["changed"]:
obj.color = (0.0, 0.0, 1.0, 0.2)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
class SelectDiffOldFile(bpy.types.Operator):
bl_idname = "bim.select_diff_old_file"
bl_label = "Select Diff Old File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectDiffNewFile(bpy.types.Operator):
bl_idname = "bim.select_diff_new_file"
bl_label = "Select Diff New File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcDiff(bpy.types.Operator):
bl_idname = "bim.execute_ifc_diff"
bl_label = "Execute IFC Diff"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
import ifcdiff
ifc_diff = ifcdiff.IfcDiff(
bpy.context.scene.DiffProperties.diff_old_file,
bpy.context.scene.DiffProperties.diff_new_file,
self.filepath,
bpy.context.scene.DiffProperties.diff_relationships.split(),
)
ifc_diff.diff()
ifc_diff.export()
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
@@ -0,0 +1,21 @@
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class DiffProperties(PropertyGroup):
diff_json_file: StringProperty(default="", name="Diff JSON File")
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
diff_new_file: StringProperty(default="", name="Diff New IFC File")
diff_relationships: StringProperty(default="", name="Diff Relationships")
@@ -0,0 +1,40 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_diff(Panel):
bl_label = "IFC Diff"
bl_idname = "BIM_PT_diff"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
bim_properties = scene.DiffProperties
layout.label(text="IFC Diff Setup:")
row = layout.row(align=True)
row.prop(bim_properties, "diff_old_file")
row.operator("bim.select_diff_old_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_new_file")
row.operator("bim.select_diff_new_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_relationships")
row = layout.row()
row.operator("bim.execute_ifc_diff")
# TODO: show if there ifc diff operation is sucessful
row = layout.row(align=True)
row.prop(bim_properties, "diff_json_file")
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
@@ -0,0 +1,22 @@
import bpy
from . import ui, operator
classes = (
operator.EditObjectPlacement,
operator.AddRepresentation,
operator.SwitchRepresentation,
operator.RemoveRepresentation,
operator.UpdateMeshRepresentation,
operator.UpdateParametricRepresentation,
operator.GetRepresentationIfcParameters,
ui.BIM_PT_representations,
ui.BIM_PT_mesh,
)
def register():
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
def unregister():
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
@@ -0,0 +1,155 @@
import ifcopenshell.util.unit
class Usecase:
def __init__(self, file, settings=None):
# TODO: This usecase currently depends on Blender's data model
self.file = file
self.settings = {
"context": None, # IfcGeometricRepresentationContext
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
"total_items": 1, # How many representation items to create
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
"is_wireframe": False, # If the geometry is a wireframe
"is_curve": False, # If the geometry is a Blender curve
"is_point_cloud": False, # If the geometry is a point cloud
}
self.ifc_vertices = []
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["unit_scale"] is None:
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
if self.settings["context"].ContextType == "Model":
return self.create_model_representation()
elif self.settings["context"].ContextType == "Plan":
return self.create_plan_representation()
return self.create_variable_representation()
def create_model_representation(self):
if self.settings["context"].is_a() == "IfcGeometricRepresentationContext":
return self.create_variable_representation()
if self.settings["context"].ContextIdentifier == "Annotation":
return self.create_geometric_set_representation()
elif self.settings["context"].ContextIdentifier == "Axis":
return self.create_curve3d_representation()
elif self.settings["context"].ContextIdentifier == "Body":
return self.create_variable_representation()
elif self.settings["context"].ContextIdentifier == "Box":
return self.create_box_representation()
elif self.settings["context"].ContextIdentifier == "Clearance":
return self.create_variable_representation()
elif self.settings["context"].ContextIdentifier == "CoG":
return self.create_cog_representation()
elif self.settings["context"].ContextIdentifier == "FootPrint":
return self.create_variable_representation()
elif self.settings["context"].ContextIdentifier == "Reference":
if self.settings["context"].TargetView == "GRAPH_VIEW":
return self.create_structural_reference_representation()
elif self.settings["context"].ContextIdentifier == "Profile":
return self.create_curve3d_representation()
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
return self.create_geometric_curve_set_representation()
def create_plan_representation(self):
if self.settings["context"].ContextIdentifier == "Annotation":
if self.settings["is_text"]:
return self.create_text_representation()
shape_representation = self.create_geometric_curve_set_representation(is_2d=True)
shape_representation.RepresentationType = "Annotation2D"
return shape_representation
elif self.settings["context"].ContextIdentifier == "Axis":
return self.create_curve2d_representation()
elif self.settings["context"].ContextIdentifier == "Body":
pass
elif self.settings["context"].ContextIdentifier == "Box":
pass
elif self.settings["context"].ContextIdentifier == "Clearance":
pass
elif self.settings["context"].ContextIdentifier == "CoG":
pass
elif self.settings["context"].ContextIdentifier == "FootPrint":
if self.settings["context"].TargetView in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
return self.create_geometric_curve_set_representation(is_2d=True)
elif self.settings["context"].ContextIdentifier == "Reference":
pass
elif self.settings["context"].ContextIdentifier == "Profile":
pass
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
pass
def create_variable_representation(self):
if self.settings["is_wireframe"]:
return self.create_wireframe_representation()
elif self.settings["is_curve"]:
return self.create_curve_representation()
elif self.settings["is_point_cloud"]:
return self.create_point_cloud_representation()
return self.create_mesh_representation()
def create_mesh_representation(self):
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
return self.create_faceted_brep()
return self.create_polygonal_face_set()
def create_faceted_brep(self):
self.create_vertices()
ifc_raw_items = [None] * self.settings["total_items"]
for i, value in enumerate(ifc_raw_items):
ifc_raw_items[i] = []
for polygon in self.settings["geometry"].polygons:
ifc_raw_items[polygon.material_index % self.settings["total_items"]].append(
self.file.createIfcFace(
[
self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]),
True,
)
]
)
)
# TODO: May not actually be a closed shell, but who checks anyway?
items = [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(i)) for i in ifc_raw_items if i]
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"Brep",
items,
)
def create_polygonal_face_set(self):
ifc_raw_items = [None] * self.settings["total_items"]
for i, value in enumerate(ifc_raw_items):
ifc_raw_items[i] = []
for polygon in self.settings["geometry"].polygons:
ifc_raw_items[polygon.material_index % self.settings["total_items"]].append(
self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices])
)
coordinates = self.file.createIfcCartesianPointList3D(
[self.convert_si_to_unit(v.co) for v in self.settings["geometry"].vertices]
)
items = [self.file.createIfcPolygonalFaceSet(coordinates, None, i) for i in ifc_raw_items if i]
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"Tessellation",
items,
)
def create_vertices(self, is_2d=False):
if is_2d:
for v in self.settings["geometry"].vertices:
co = self.convert_si_to_unit(v.co)
self.ifc_vertices.append(self.file.createIfcCartesianPoint((co[0], co[1])))
return
self.ifc_vertices.extend(
[
self.file.createIfcCartesianPoint(self.convert_si_to_unit(v.co))
for v in self.settings["geometry"].vertices
]
)
def convert_si_to_unit(self, co):
return co / self.settings["unit_scale"]
@@ -0,0 +1,18 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"representation": None
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
definition = self.settings["product"].Representation
if not definition:
definition = self.file.createIfcProductDefinitionShape()
self.settings["product"].Representation = definition
representations = list(definition.Representations) if definition.Representations else []
representations.append(self.settings["representation"])
definition.Representations = representations
@@ -0,0 +1,31 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"shape_representation": None,
"styles": [],
"should_use_presentation_style_assignment": False,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["styles"]:
return []
self.results = []
for element in self.file.traverse(self.settings["shape_representation"]):
if not element.is_a("IfcShapeRepresentation"):
continue
for item in element.Items:
if not item.is_a("IfcGeometricRepresentationItem"):
continue
style = self.settings["styles"].pop(0)
name = style.Name
if self.file.schema == "IFC2X3" or self.settings["should_use_presentation_style_assignment"]:
style = self.file.createIfcPresentationStyleAssignment([style])
self.results.append(
self.file.createIfcStyledItem(
item, [style], name
)
)
return self.results
@@ -0,0 +1,29 @@
from blenderbim.bim.ifc import IfcStore
class Data:
products = {}
representations = {}
@classmethod
def load(cls, product_id):
file = IfcStore.get_file()
if not file:
return
cls.products[product_id] = []
product = file.by_id(product_id)
if not hasattr(product, "Representation") or not product.Representation:
return
for representation in product.Representation.Representations:
c = representation.ContextOfItems
rep_id = int(representation.id())
cls.representations[rep_id] = {
"RepresentationIdentifier": representation.RepresentationIdentifier,
"RepresentationType": representation.RepresentationType,
"ContextOfItems": {
"ContextType": c.ContextType,
"ContextIdentifier": c.ContextIdentifier,
"TargetView": c.TargetView if c.is_a("IfcGeometricRepresentationSubContext") else "",
}
}
cls.products[product_id].append(rep_id)
@@ -0,0 +1,86 @@
import numpy as np
import ifcopenshell.util.element
import ifcopenshell.util.placement
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"product": None, "matrix": np.eye(4)}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not hasattr(self.settings["product"], "ObjectPlacement"):
return
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
dependent_objects = []
if self.settings["product"].ObjectPlacement:
for referenced_placement in self.settings["product"].ObjectPlacement.ReferencedByPlacements:
for placed_obj in referenced_placement.PlacesObject:
dependent_objects.append(
{
"product": placed_obj,
"matrix": ifcopenshell.util.placement.get_local_placement(referenced_placement),
}
)
placement_rel_to = None
if hasattr(self.settings["product"], "ContainedInStructure") and self.settings["product"].ContainedInStructure:
placement_rel_to = self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement
elif hasattr(self.settings["product"], "Decomposes") and self.settings["product"].Decomposes:
relating_object = self.settings["product"].Decomposes[0].RelatingObject
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to))
if self.settings["product"].ObjectPlacement:
for inverse in self.file.get_inverse(self.settings["product"]):
ifcopenshell.util.element.replace_attribute(
inverse, self.settings["product"].ObjectPlacement, placement
)
self.file.remove(self.settings["product"].ObjectPlacement)
self.settings["product"].ObjectPlacement = placement
for settings in dependent_objects:
self.settings = settings
self.execute()
return placement
def get_relative_placement(self, placement_rel_to):
if placement_rel_to:
relating_object_matrix = ifcopenshell.util.placement.get_local_placement(placement_rel_to)
relating_object_matrix[0][3] = self.convert_unit_to_si(relating_object_matrix[0][3])
relating_object_matrix[1][3] = self.convert_unit_to_si(relating_object_matrix[1][3])
relating_object_matrix[2][3] = self.convert_unit_to_si(relating_object_matrix[2][3])
else:
relating_object_matrix = np.eye(4)
m = self.settings["matrix"]
x = np.array((m[0][0], m[1][0], m[2][0]))
z = np.array((m[0][2], m[1][2], m[2][2]))
o = np.array((m[0][3], m[1][3], m[2][3]))
object_matrix = ifcopenshell.util.placement.a2p(o, z, x)
relative_placement_matrix = np.linalg.inv(relating_object_matrix) @ object_matrix
return self.create_ifc_axis_2_placement_3d(
relative_placement_matrix[:, 3][0:3],
relative_placement_matrix[:, 2][0:3],
relative_placement_matrix[:, 0][0:3],
)
def create_ifc_axis_2_placement_3d(self, point, up, forward):
return self.file.createIfcAxis2Placement3D(
self.create_cartesian_point(point),
self.file.createIfcDirection(up.tolist()),
self.file.createIfcDirection(forward.tolist()),
)
def create_cartesian_point(self, co):
co = self.convert_si_to_unit(co)
return self.file.createIfcCartesianPoint(co.tolist())
def convert_si_to_unit(self, co):
return co / self.unit_scale
def convert_unit_to_si(self, co):
return co * self.unit_scale
@@ -0,0 +1,253 @@
import bpy
import numpy as np
import ifcopenshell
import logging
import blenderbim.bim.module.geometry.edit_object_placement as edit_object_placement
import blenderbim.bim.module.geometry.add_representation as add_representation
import blenderbim.bim.module.geometry.assign_styles as assign_styles
import blenderbim.bim.module.geometry.assign_representation as assign_representation
import blenderbim.bim.module.geometry.remove_representation as remove_representation
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.data import Data
class EditObjectPlacement(bpy.types.Operator):
bl_idname = "bim.edit_object_placement"
bl_label = "Edit Object Placement"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
props = bpy.context.scene.BIMGeoreferenceProperties
matrix = np.array(obj.matrix_world)
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
self.calculate_unit_scale()
# TODO: np.array? Why not matrix?
matrix = np.array(
ifcopenshell.util.geolocation.local2global(
np.matrix(obj.matrix_world),
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
)
edit_object_placement.Usecase(
self.file,
{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"matrix": matrix,
},
).execute()
return {"FINISHED"}
def calculate_unit_scale(self):
self.unit_scale = 1
units = self.file.by_type("IfcUnitAssignment")[0]
for unit in units.Units:
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
continue
while unit.is_a("IfcConversionBasedUnit"):
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
unit = unit.ConversionFactor.UnitComponent
if unit.is_a("IfcSIUnit"):
self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
class AddRepresentation(bpy.types.Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
obj: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
bpy.ops.bim.edit_object_placement(obj=obj.name)
if obj.data:
result = add_representation.Usecase(
self.file,
{
"context": self.file.by_id(context_id),
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
},
).execute()
if not result:
print("Failed to write shape representation")
return {"FINISHED"}
assign_styles.Usecase(
self.file,
{
"shape_representation": result,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
},
).execute()
assign_representation.Usecase(
self.file,
{"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), "representation": result},
).execute()
existing_mesh = obj.data
mesh = obj.data.copy()
mesh.name = "{}/{}".format(context_id, result.id())
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
obj.data = mesh
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class SwitchRepresentation(bpy.types.Operator):
bl_idname = "bim.switch_representation"
bl_label = "Switch Representation"
ifc_definition_id: bpy.props.IntProperty()
def execute(self, context):
self.obj = bpy.context.active_object
self.file = IfcStore.get_file()
context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
self.mesh_name = "{}/{}".format(context_of_items.id(), self.ifc_definition_id)
mesh = bpy.data.meshes.get(self.mesh_name)
if mesh:
self.obj.data.user_remap(mesh)
self.pull_mesh_from_ifc()
return {"FINISHED"}
def pull_mesh_from_ifc(self):
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
element = self.file.by_id(self.obj.BIMObjectProperties.ifc_definition_id)
settings = ifcopenshell.geom.settings()
settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
self.obj.data.user_remap(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
material_creator.create(element, self.obj, mesh)
class RemoveRepresentation(bpy.types.Operator):
bl_idname = "bim.remove_representation"
bl_label = "Remove Representation"
ifc_definition_id: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.ifc_definition_id)
obj = bpy.context.active_object
mesh = bpy.data.meshes.get("{}/{}".format(representation.ContextOfItems.id(), representation.id()))
if mesh:
if obj.data == mesh:
# TODO we can do better than this
void_mesh = bpy.data.meshes.get("Void")
if not void_mesh:
void_mesh = bpy.data.meshes.new("Void")
obj.data = void_mesh
bpy.data.meshes.remove(mesh)
remove_representation.Usecase(self.file, {"representation": representation}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UpdateMeshRepresentation(bpy.types.Operator):
bl_idname = "bim.update_mesh_representation"
bl_label = "Update Mesh Representation"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
bpy.ops.bim.edit_object_placement(obj=obj.name)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
new_representation = add_representation.Usecase(
self.file,
{
"context": old_representation.ContextOfItems,
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
},
).execute()
if not new_representation:
print("Failed to write shape representation")
return {"FINISHED"}
assign_styles.Usecase(
self.file,
{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
},
).execute()
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UpdateParametricRepresentation(bpy.types.Operator):
bl_idname = "bim.update_parametric_representation"
bl_label = "Update Parametric Representation"
index: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
props = obj.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index]
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id)
return {"FINISHED"}
class GetRepresentationIfcParameters(bpy.types.Operator):
bl_idname = "bim.get_representation_ifc_parameters"
bl_label = "Get Representation IFC Parameters"
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
props = obj.data.BIMMeshProperties
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
for element in elements:
if not element.is_a("IfcRepresentationItem"):
continue
for i in range(0, len(element)):
if element.attribute_type(i) == "DOUBLE":
new = props.ifc_parameters.add()
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
return {"FINISHED"}
@@ -0,0 +1,22 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"representation": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
styles = []
for subelement in self.file.traverse(self.settings["representation"]):
if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem:
styles.append(subelement)
for style in styles:
self.remove_deep(style)
self.remove_deep(self.settings["representation"])
def remove_deep(self, element):
subgraph = list(self.file.traverse(element))
subgraph_set = set(subgraph)
for ref in subgraph[::-1]:
if ref.id() and len(set(self.file.get_inverse(ref)) - subgraph_set) == 0:
self.file.remove(ref)
@@ -0,0 +1,75 @@
import bpy
from bpy.types import Panel
from blenderbim.bim.module.geometry.data import Data
class BIM_PT_representations(Panel):
bl_label = "IFC Representations"
bl_idname = "BIM_PT_representations"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
layout = self.layout
props = context.active_object.BIMObjectProperties
if props.ifc_definition_id not in Data.products:
Data.load(props.ifc_definition_id)
representations = Data.products[props.ifc_definition_id]
if not representations:
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(bpy.context.scene.BIMProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for ifc_definition_id in representations:
representation = Data.representations[ifc_definition_id]
row = self.layout.row(align=True)
row.label(text=representation["ContextOfItems"]["ContextType"])
row.label(text=representation["ContextOfItems"]["ContextIdentifier"])
row.label(text=representation["ContextOfItems"]["TargetView"])
row.label(text=representation["RepresentationType"])
row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="").ifc_definition_id = ifc_definition_id
row.operator("bim.remove_representation", icon="X", text="").ifc_definition_id = ifc_definition_id
class BIM_PT_mesh(Panel):
bl_label = "IFC Representation"
bl_idname = "BIM_PT_mesh"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
@classmethod
def poll(cls, context):
return (
context.active_object is not None
and context.active_object.type == "MESH"
and hasattr(context.active_object.data, "BIMMeshProperties")
)
def draw(self, context):
if not context.active_object.data:
return
layout = self.layout
props = context.active_object.data.BIMMeshProperties
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
row = layout.row()
row.operator("bim.update_mesh_representation")
for index, ifc_parameter in enumerate(props.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, "name", text="")
row.prop(ifc_parameter, "value", text="")
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
def BIM_PT_transform(self, context):
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
row = self.layout.row()
row.operator("bim.edit_object_placement")
@@ -0,0 +1,25 @@
import bpy
from . import ui, prop, operator
classes = (
operator.EnableEditingGeoreferencing,
operator.DisableEditingGeoreferencing,
operator.EditGeoreferencing,
operator.SetNorthOffset,
operator.GetNorthOffset,
operator.RemoveGeoreferencing,
operator.AddGeoreferencing,
operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_utilities,
)
def register():
bpy.types.Scene.BIMGeoreferenceProperties = bpy.props.PointerProperty(type=prop.BIMGeoreferenceProperties)
def unregister():
del bpy.types.Scene.BIMGeoreferenceProperties
@@ -0,0 +1,20 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
def execute(self):
source_crs = None
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.ContextType == "Model":
source_crs = context
break
if not source_crs:
return
projected_crs = self.file.create_entity("IfcProjectedCRS", **{"Name": ""})
self.file.create_entity("IfcMapConversion", **{
"SourceCRS": source_crs,
"TargetCRS": projected_crs,
"Eastings": 0,
"Northings": 0,
"OrthogonalHeight": 0,
})
@@ -0,0 +1,29 @@
from blenderbim.bim.ifc import IfcStore
class Data:
is_loaded = False
map_conversion = {}
projected_crs = {}
@classmethod
def load(cls):
file = IfcStore.get_file()
if not file:
return
cls.map_conversion = {}
cls.projected_crs = {}
if file.schema == "IFC2X3":
return
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if not context.HasCoordinateOperation:
continue
map_conversion = context.HasCoordinateOperation[0]
cls.map_conversion = map_conversion.get_info()
cls.map_conversion["SourceCRS"] = cls.map_conversion["SourceCRS"].id()
cls.map_conversion["TargetCRS"] = cls.map_conversion["TargetCRS"].id()
cls.projected_crs = map_conversion.TargetCRS.get_info()
if cls.projected_crs["MapUnit"]:
cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info()
break
cls.is_loaded = True
@@ -0,0 +1,52 @@
import ifcopenshell.util.unit
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"map_conversion": {},
"projected_crs": {},
"map_unit": "",
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
map_conversion = self.file.by_type("IfcMapConversion")[0]
projected_crs = self.file.by_type("IfcProjectedCRS")[0]
for name, value in self.settings["map_conversion"].items():
setattr(map_conversion, name, value)
for name, value in self.settings["projected_crs"].items():
setattr(projected_crs, name, value)
self.remove_existing_map_unit(projected_crs)
self.set_map_unit(projected_crs)
def remove_existing_map_unit(self, projected_crs):
if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1:
# TODO: go deeper for conversion units
self.file.remove(projected_crs.MapUnit)
def set_map_unit(self, projected_crs):
if not self.settings["map_unit"]:
return
if "METRE" in self.settings["map_unit"]:
projected_crs.MapUnit = self.file.createIfcSIUnit(
None,
"LENGTHUNIT",
ifcopenshell.util.unit.get_prefix(self.settings["map_unit"]),
ifcopenshell.util.unit.get_unit_name(self.settings["map_unit"]),
)
return
value_component = self.file.create_entity(
"IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[self.settings["map_unit"]]}
)
si_unit = self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
projected_crs.MapUnit = self.file.createIfcConversionBasedUnit(
self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0),
"LENGTHUNIT",
self.settings["map_unit"],
self.file.createIfcMeasureWithUnit(value_component, si_unit),
)
@@ -0,0 +1,242 @@
import bpy
import json
import ifcopenshell
import ifcopenshell.util.unit
import blenderbim.bim.module.georeference.add_georeferencing as add_georeferencing
import blenderbim.bim.module.georeference.edit_georeferencing as edit_georeferencing
import blenderbim.bim.module.georeference.remove_georeferencing as remove_georeferencing
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.georeference.data import Data
from math import radians, degrees, atan, tan, cos, sin
class EnableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing"
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
while len(props.map_conversion) > 0:
props.map_conversion.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = str(attribute.type_of_attribute)
if "<entity" in data_type:
continue
new = props.map_conversion.add()
new.name = attribute.name()
new.is_null = Data.map_conversion[attribute.name()] is None
new.is_optional = attribute.optional()
if "<string>" in data_type:
new.string_value = "" if new.is_null else Data.map_conversion[attribute.name()]
new.data_type = "string"
elif "<real>" in data_type:
new.float_value = 0.0 if new.is_null else Data.map_conversion[attribute.name()]
new.data_type = "float"
elif "<integer>" in data_type:
new.int_value = 0 if new.is_null else Data.map_conversion[attribute.name()]
new.data_type = "integer"
elif "<boolean>" in data_type or "<logical>" in data_type:
new.bool_value = False if new.is_null else Data.map_conversion[attribute.name()]
new.data_type = "boolean"
while len(props.projected_crs) > 0:
props.projected_crs.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
data_type = str(attribute.type_of_attribute)
if "<entity" in data_type:
continue
new = props.projected_crs.add()
new.name = attribute.name()
new.is_null = Data.projected_crs[attribute.name()] is None
new.is_optional = attribute.optional()
if "<string>" in data_type:
new.string_value = "" if new.is_null else Data.projected_crs[attribute.name()]
new.data_type = "string"
elif "<real>" in data_type:
new.float_value = 0.0 if new.is_null else Data.projected_crs[attribute.name()]
new.data_type = "float"
elif "<integer>" in data_type:
new.int_value = 0 if new.is_null else Data.projected_crs[attribute.name()]
new.data_type = "integer"
elif "<boolean>" in data_type or "<logical>" in data_type:
new.bool_value = False if new.is_null else Data.projected_crs[attribute.name()]
new.data_type = "boolean"
props.is_map_unit_null = Data.projected_crs["MapUnit"] is None
if not props.is_map_unit_null:
props.map_unit_type = Data.projected_crs["MapUnit"]["type"]
if props.map_unit_type == "IfcSIUnit":
prefix = ifcopenshell.util.unit.get_prefix(Data.projected_crs["MapUnit"]["Prefix"]) or ""
name = ifcopenshell.util.unit.get_unit_name(Data.projected_crs["MapUnit"]["Name"])
props.map_unit_si = prefix + name
elif props.map_unit_type == "IfcConversionBasedUnit":
props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"]
props.is_editing = True
return {"FINISHED"}
class DisableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing"
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
props.is_editing = False
return {"FINISHED"}
class EditGeoreferencing(bpy.types.Operator):
bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing"
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
map_conversion = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = str(attribute.type_of_attribute)
if "<entity" in data_type:
continue
blender_attribute = props.map_conversion.get(attribute.name())
if blender_attribute.is_null:
map_conversion[attribute.name()] = None
elif blender_attribute.data_type == "string":
map_conversion[attribute.name()] = blender_attribute.string_value
elif blender_attribute.data_type == "float":
map_conversion[attribute.name()] = blender_attribute.float_value
elif blender_attribute.data_type == "integer":
map_conversion[attribute.name()] = blender_attribute.int_value
elif blender_attribute.data_type == "boolean":
map_conversion[attribute.name()] = blender_attribute.bool_value
projected_crs = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
data_type = str(attribute.type_of_attribute)
if "<entity" in data_type:
continue
blender_attribute = props.projected_crs.get(attribute.name())
if blender_attribute.is_null:
projected_crs[attribute.name()] = None
elif blender_attribute.data_type == "string":
projected_crs[attribute.name()] = blender_attribute.string_value
elif blender_attribute.data_type == "float":
projected_crs[attribute.name()] = blender_attribute.float_value
elif blender_attribute.data_type == "integer":
projected_crs[attribute.name()] = blender_attribute.int_value
elif blender_attribute.data_type == "boolean":
projected_crs[attribute.name()] = blender_attribute.bool_value
map_unit = ""
if not props.is_map_unit_null:
map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
edit_georeferencing.Usecase(self.file, {
"map_conversion": map_conversion,
"projected_crs": projected_crs,
"map_unit": map_unit
}).execute()
Data.load()
bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"}
class SetNorthOffset(bpy.types.Operator):
bl_idname = "bim.set_north_offset"
bl_label = "Set North Offset"
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.xy2angle(
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value,
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value
)
)
return {"FINISHED"}
class GetNorthOffset(bpy.types.Operator):
bl_idname = "bim.get_north_offset"
bl_label = "Get North Offset"
def execute(self, context):
x_angle = -context.scene.sun_pos_properties.north_offset
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value = cos(x_angle)
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value = sin(x_angle)
return {"FINISHED"}
class RemoveGeoreferencing(bpy.types.Operator):
bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing"
def execute(self, context):
remove_georeferencing.Usecase(IfcStore.get_file()).execute()
Data.load()
return {"FINISHED"}
class AddGeoreferencing(bpy.types.Operator):
bl_idname = "bim.add_georeferencing"
bl_label = "Add Georeferencing"
def execute(self, context):
add_georeferencing.Usecase(IfcStore.get_file()).execute()
Data.load()
return {"FINISHED"}
class ConvertLocalToGlobal(bpy.types.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
def execute(self, context):
if not Data.is_loaded:
Data.load()
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
props.coordinate_output = ",".join([str(r) for r in results])
bpy.context.scene.cursor.location = results
return {"FINISHED"}
class ConvertGlobalToLocal(bpy.types.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
def execute(self, context):
if not Data.is_loaded:
Data.load()
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
results = ifcopenshell.util.geolocation.enh2xyz(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
props.coordinate_output = ",".join([str(r) for r in results])
bpy.context.scene.cursor.location = results
return {"FINISHED"}
@@ -0,0 +1,48 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMGeoreferenceProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
map_conversion: CollectionProperty(name="Map Conversion", type=Attribute)
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
map_unit_type: EnumProperty(
items=[(n, n, "") for n in ["IfcSIUnit", "IfcConversionBasedUnit"]],
name="Map Unit Type",
default="IfcSIUnit",
)
map_unit_si: EnumProperty(
items=[(n, n.lower().capitalize(), "") for n in ["MILLIMETRE", "CENTIMETRE", "METRE", "KILOMETRE"]],
name="Map Unit SI",
default="METRE",
)
map_unit_imperial: EnumProperty(
items=[(n, n.lower().capitalize(), "") for n in ["inch", "foot", "yard", "mile"]],
name="Map Unit SI",
default="foot",
)
is_map_unit_null: BoolProperty(name="Is Map Unit Null")
coordinate_input: StringProperty(name="Coordinate Input")
coordinate_output: StringProperty(name="Coordinate Output")
has_blender_offset: BoolProperty(name="Has Blender Offset")
blender_offset_type: EnumProperty(
items=[(o, o, "") for o in ["OBJECT_PLACEMENT", "CARTESIAN_POINT"]],
name="Blender Offset",
default="OBJECT_PLACEMENT",
)
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0")
@@ -0,0 +1,12 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
def execute(self):
map_conversion = self.file.by_type("IfcMapConversion")[0]
projected_crs = self.file.by_type("IfcProjectedCRS")[0]
if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1:
# TODO: go deeper for conversion units
self.file.remove(projected_crs.MapUnit)
self.file.remove(projected_crs)
self.file.remove(map_conversion)
@@ -0,0 +1,153 @@
from bpy.types import Panel
from blenderbim.bim.module.georeference.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing"
bl_idname = "BIM_PT_gis"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
props = context.scene.BIMGeoreferenceProperties
if not Data.is_loaded:
Data.load()
if props.is_editing:
return self.draw_editable_ui(context)
self.draw_ui(context)
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_georeferencing", icon="X", text="")
for attribute in props.map_conversion:
if attribute.name == "XAxisAbscissa" and hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.get_north_offset", text="Set IFC North")
row.operator("bim.set_north_offset", text="Set Blender North")
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
for attribute in props.projected_crs:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
row = self.layout.row(align=True)
row.prop(props, "map_unit_type", text="MapUnit")
if props.map_unit_type == "IfcSIUnit":
row.prop(props, "map_unit_si", text="")
elif props.map_unit_type == "IfcConversionBasedUnit":
row.prop(props, "map_unit_imperial", text="")
row.prop(props, "is_map_unit_null", icon="RADIOBUT_OFF" if props.is_map_unit_null else "RADIOBUT_ON", text="")
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
if not Data.map_conversion and IfcStore.get_file().schema != "IFC2X3":
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset:
row = self.layout.row()
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
row = self.layout.row(align=True)
row.label(text="Type")
row.label(text=props.blender_offset_type)
row = self.layout.row(align=True)
row.label(text="Eastings")
row.label(text=props.blender_eastings)
row = self.layout.row(align=True)
row.label(text="Northings")
row.label(text=props.blender_northings)
row = self.layout.row(align=True)
row.label(text="OrthogonalHeight")
row.label(text=props.blender_orthogonal_height)
row = self.layout.row(align=True)
row.label(text="XAxisAbscissa")
row.label(text=props.blender_x_axis_abscissa)
row = self.layout.row(align=True)
row.label(text="XAxisOrdinate")
row.label(text=props.blender_x_axis_ordinate)
elif IfcStore.get_file().schema == "IFC2X3":
row = self.layout.row()
row.label(text="Not Georeferenced")
if Data.map_conversion:
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in Data.map_conversion.items():
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or not value:
continue
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
if Data.projected_crs:
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
for key, value in Data.projected_crs.items():
if key == "id" or key == "type" or not value:
continue
if key == "MapUnit":
unit_value = value.get("Prefix", "") or ""
unit_value += value["Name"]
value = unit_value
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
class BIM_PT_gis_utilities(Panel):
bl_idname = "BIM_PT_gis_utilities"
bl_label = "Georeferencing Utilities"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
def draw(self, context):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row()
row.prop(props, "coordinate_input", text="Input")
row = self.layout.row()
row.prop(props, "coordinate_output", text="Output")
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
@@ -0,0 +1,29 @@
import bpy
from . import ui, prop, operator
classes = (
operator.AssignMaterial,
operator.UnassignMaterial,
operator.AddConstituent,
operator.RemoveConstituent,
operator.AddLayer,
operator.RemoveLayer,
operator.AddListItem,
operator.RemoveListItem,
operator.EnableEditingAssignedMaterial,
operator.DisableEditingAssignedMaterial,
operator.EditAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.DisableEditingMaterialSetItem,
operator.EditMaterialSetItem,
prop.BIMObjectMaterialProperties,
ui.BIM_PT_object_material,
)
def register():
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
def unregister():
del bpy.types.Object.BIMObjectMaterialProperties
@@ -0,0 +1,16 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"constituent_set": None, "material": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
constituents = list(self.settings["constituent_set"].MaterialConstituents)
constituent = self.file.create_entity("IfcMaterialConstituent", **{"Material": self.settings["material"]})
constituents.append(constituent)
self.settings["constituent_set"].MaterialConstituents = constituents
return constituent

Some files were not shown because too many files have changed in this diff Show More