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(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(IFCCONVERT_DOUBLE_PRECISION "IfcConvert: Use double precision floating-point numbers." ON)
OPTION(BUILD_IFCGEOM "Build IfcGeom." 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_EXAMPLES "Build example applications." ON)
OPTION(BUILD_GEOMSERVER "Build IfcGeomServer executable." ON) OPTION(BUILD_GEOMSERVER "Build IfcGeomServer executable." ON)
OPTION(BUILD_CONVERT "Build IfcConvert 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 # get from userdata
userdata = context.config.userdata userdata = context.config.userdata
context.ifcbasename = userdata["ifcbasename"]
context.localedir = userdata.get("localedir") 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 # do not break after a failed scenario
# https://community.osarch.org/discussion/comment/3328/#Comment_3328 # 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" the_lang = "en"
@step("there are no {ifc_class} elements because {reason}") @step("There are no {ifc_class} elements")
def step_impl(context, ifc_class, reason): def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang) switch_locale(context.localedir, the_lang)
aem.no_eleclass_because_reason( aem.no_eleclass(
context, context,
ifc_class, ifc_class
reason
) )
@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): def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang) switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value( 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): def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang) switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value( 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): def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang) switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value( 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 from utils import IfcFile
def no_eleclass_because_reason( def no_eleclass(
context, ifc_class, reason context, ifc_class
): ):
context.falseelems = [] 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 given
from behave import step from behave import step
from ifcdata_methods import assert_schema import ifcdata_methods as idm
from utils import IfcFile from utils import IfcFile
from utils import switch_locale from utils import switch_locale
@@ -28,12 +28,10 @@ def step_impl(context, file):
assert False, f"The file {file} could not be loaded" 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): def step_impl(context):
try: switch_locale(context.localedir, the_lang)
IfcFile.load(context.config.userdata.get("ifcfile")) idm.provide_ifcfile_by_argument(context)
except:
assert False, f"The IFC {context.config.userdata.get('ifcfile')} file could not be loaded"
@given('A file path has been provided through an argument') @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") @step("IFC data must use the {schema} schema")
def step_impl(context, schema): def step_impl(context, schema):
switch_locale(context.localedir, the_lang) 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') @step('The IFC file "{file}" is exempt from being provided')
@@ -1,13 +1,19 @@
from behave import step from behave import step
from ifcdata_methods import assert_schema import ifcdata_methods as idm
from utils import switch_locale from utils import switch_locale
the_lang = "de" 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): def step_impl(context, schema):
switch_locale(context.localedir, the_lang) 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 behave import step
from ifcdata_methods import assert_schema import ifcdata_methods as idm
from utils import switch_locale from utils import switch_locale
the_lang = "fr" 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}") @step("Les données IFC doivent utiliser le schéma {schema}")
def step_impl(context, schema): def step_impl(context, schema):
switch_locale(context.localedir, the_lang) 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 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 real_schema = IfcFile.get().schema
assert real_schema == target_schema, ( assert real_schema == target_schema, (
_("We expected a schema of {} but instead got {}") _("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: if browse widgets will be canceled, last QLineEdit should be restored
# TODO: keep path or file if in browse widget canceled # 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 import os
@@ -15,25 +18,32 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def __init__( def __init__(
self, self,
features="", featurespath="",
ifcfile="" ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
): ):
super(GuiWidgetBimTester, self).__init__() super(GuiWidgetBimTester, self).__init__()
# get features dir
user_path = os.path.expanduser("~") user_path = os.path.expanduser("~")
# print(features)
self.initial_featurespath = features # get features dir
if not os.path.isdir(self.initial_featurespath): # print(featurespath)
self.initial_featurespath = user_path if not os.path.isdir(featurespath):
print(self.initial_featurespath) featurespath = user_path
# get ifc file # get ifc file
# print(ifcfile) # print(ifcfile)
if not os.path.isfile(ifcfile):
ifcfile = user_path
self.initial_featurespath = featurespath
self.initial_ifcfile = ifcfile self.initial_ifcfile = ifcfile
if not os.path.isfile(self.initial_ifcfile): self.get_featurepath_from_ifcpath = get_featurepath_from_ifcpath
self.initial_ifcfile = user_path self.args = args
print(self.initial_ifcfile) # print(self.initial_featurespath)
# print(self.initial_ifcfile)
# print(self.get_featurepath_from_ifcpath)
# init ui # init ui
self._setup_ui() self._setup_ui()
@@ -143,6 +153,9 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.setRowStretch(0, 10) layout.setRowStretch(0, 10)
self.setLayout(layout) 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): def select_ifcfile(self):
# print(self.get_ifcfile()) # print(self.get_ifcfile())
@@ -162,11 +175,8 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def featuredirfromifc_clicked(self): def featuredirfromifc_clicked(self):
if self.featuredirfromifc_cb.isChecked() is True: if self.featuredirfromifc_cb.isChecked() is True:
self.set_featurefilesdir("") self.set_featurefilesdir("")
# TODO
self.featurefilesdir_text.setEnabled(False) self.featurefilesdir_text.setEnabled(False)
self.feafilesdir_browse_btn.setEnabled(False) self.feafilesdir_browse_btn.setEnabled(False)
# deactivate feature path browser button
# deactivate lineedit text
else: else:
self.set_featurefilesdir(self.initial_featurespath) self.set_featurefilesdir(self.initial_featurespath)
self.featurefilesdir_text.setEnabled(True) self.featurefilesdir_text.setEnabled(True)
@@ -193,18 +203,42 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# ********************************************************** # **********************************************************
def run_bimtester(self): def run_bimtester(self):
print("Run BIMTester") print("Run BIMTester by the GUI")
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
# get features dir # get features dir
if self.featuredirfromifc_cb.isChecked() is True: if self.featuredirfromifc_cb.isChecked() is True:
the_features_path = os.path.dirname(os.path.realpath( ifcfile = self.get_ifcfile()
self.get_ifcfile()
))
print( print(
"Make sure the feature files are beside " "Make sure the feature files are beside "
"the ifc file in a directory named 'features'." "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: else:
the_features_path = self.get_featurefilesdir() the_features_path = self.get_featurefilesdir()
print(the_features_path) print(the_features_path)
@@ -213,11 +247,13 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
the_ifcfile = self.get_ifcfile() the_ifcfile = self.get_ifcfile()
print(the_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 # run bimtester
status = run_all( status = run_all(patched_args)
the_features_path,
the_ifcfile,
)
print(status) print(status)
QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QApplication.restoreOverrideCursor()
@@ -7,150 +7,158 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2020-12-23 11:20+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: de\n" "Language: de\n"
"Language-Team: de <LL@li.org>\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" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n" "Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160 #: reports.py:158
msgid "OpenBIM auditing is a feature of"
msgstr "OpenBIM auditing ist eine Funktionalität von"
#: reports.py:155
msgid "en" msgid "en"
msgstr "de" msgstr "de"
#: reports.py:156 #: reports.py:159
msgid "Success" msgid "Success"
msgstr "Bestanden" msgstr "Bestanden"
#: reports.py:157 #: reports.py:160
msgid "Failure" msgid "Failure"
msgstr "Durchgefallen" msgstr "Durchgefallen"
#: reports.py:158 #: reports.py:161
msgid "Tests passed" msgid "Tests passed"
msgstr "Erfolgreiche Tests" msgstr "Erfolgreiche Tests"
#: reports.py:159 #: reports.py:162
msgid "Duration" msgid "Duration"
msgstr "Dauer" 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" msgid "and"
msgstr "und" 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}." msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "" 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}" msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr "" 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." msgid "Error in falsecount, something went wrong."
msgstr "Fehler in falsecount, es ist etwas falsch gelaufen." msgstr "Fehler in falsecount, es ist etwas falsch gelaufen."
#: features/steps/attributes_eleclasses_methods.py:80 #: features/steps/attributes_eleclasses_methods.py:84
msgid "" msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes " "For all {elemcount} {ifc_class} elements at least one of these class "
"{parameter} has no value." "attributes {parameter} has no value."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81 #: features/steps/attributes_eleclasses_methods.py:85
msgid "" msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least " "For the following {falsecount} out of {elemcount} {ifc_class} elements at"
"one of these class attributes {parameter} has no value: {falseelems}" " least one of these class attributes {parameter} has no value: "
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: "
"{falseelems}" "{falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131 #: features/steps/attributes_eleclasses_methods.py:86
msgid "The description of all {elemcount} {elemcount} elements is not set." #: 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 "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132 #: features/steps/attributes_eleclasses_methods.py:111
msgid "" 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}" "set: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:29 #: features/steps/attributes_eleclasses_methods.py:137
msgid "" msgid "The description of all {elemcount} {elemcount} elements is not set."
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:30 #: features/steps/attributes_eleclasses_methods.py:138
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the " "The description of {falsecount} out of {elemcount} {ifc_class} elements "
"property {parameter} in the pset: {falseelems}" "is not set: {falseelems}"
msgstr "" 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." msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr "" msgstr ""
"Alle {elemcount} {ifc_class} Bauteile haben keine geometrische Repräsentation " "Alle {elemcount} {ifc_class} Bauteile haben keine geometrische "
"der Klasse {parameter}." "Repräsentation der Klasse {parameter}."
#: features/steps/geometric_detail_methods.py:51 #: features/steps/geometric_detail_methods.py:55
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a " "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}" "{parameter} representation: {falseelems}"
msgstr "" msgstr ""
"Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen " "Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} "
"haben keine geometrische Repräsentation der Klasse {parameter}: {falseelems}" "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." msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr "" msgstr ""
"Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} Bauteilen " "Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} "
"haben Fehler." "Bauteilen haben Fehler."
#: features/steps/geometric_detail_methods.py:126 #: features/steps/geometric_detail_methods.py:133
msgid "" msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have " "The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"errors: {falseelems}" "have errors: {falseelems}"
msgstr "" msgstr ""
"Die geometrischen Repräsentationen von {falsecount} von allen {elemcount} " "Die geometrischen Repräsentationen von {falsecount} von allen {elemcount}"
"{ifc_class} Bauteilen haben Fehler: {falseelems}" " {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 {}" msgid "We expected a schema of {} but instead got {}"
msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}" msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}"
#~ msgid "The geometry of all {} {} elements have errors." #~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr "" #~ 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: {}" #~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr "" #~ msgstr ""
#~ "Die geometrischen Repräsentationen von {} von allen {} {} Bauteilen haben " #~ "Die geometrischen Repräsentationen von {} "
#~ "von allen {} {} Bauteilen haben "
#~ "Fehler: {}" #~ "Fehler: {}"
#~ msgid "There are no {} elements in the IFC file." #~ 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." #~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr "" #~ msgstr ""
#~ "Alle {} {} Bauteile haben keine geometrische Repräsentation der Klasse " #~ "Alle {} {} Bauteile haben keine "
#~ "geometrische Repräsentation der Klasse "
#~ "IfcFacetedBrep." #~ "IfcFacetedBrep."
#~ msgid "" #~ 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 "" #~ msgid ""
#~ "All {elemcount} {ifc_class} elements are not a IfcFacetedBrep representation." #~ "All {elemcount} {ifc_class} elements are "
#~ "not a IfcFacetedBrep representation."
#~ msgstr "" #~ msgstr ""
#~ "Alle {elemcount} {ifc_class} Bauteile haben keine geometrische " #~ "Alle {elemcount} {ifc_class} Bauteile haben"
#~ "Repräsentation der Klasse IfcFacetedBrep." #~ " keine geometrische Repräsentation der "
#~ "Klasse IfcFacetedBrep."
#~ msgid "" #~ 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}" #~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr "" #~ msgstr ""
#~ "Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen " #~ "Die Anzahl Bauteile {falsecount} von "
#~ "haben keine geometrische Repräsentation der Klasse IfcFacetedBrep: " #~ "allen {elemcount} {ifc_class} Bauteilen haben"
#~ "{falseelems}" #~ " keine geometrische Repräsentation der "
#~ "Klasse IfcFacetedBrep: {falseelems}"
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2020-12-18 11:41+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: en\n" "Language: en\n"
@@ -18,122 +18,126 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n" "Generated-By: Babel 2.6.0\n"
#: reports.py:153 reports.py:160 #: reports.py:158
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
msgid "en" msgid "en"
msgstr "" msgstr ""
#: reports.py:156 #: reports.py:159
msgid "Success" msgid "Success"
msgstr "" msgstr ""
#: reports.py:157 #: reports.py:160
msgid "Failure" msgid "Failure"
msgstr "" msgstr ""
#: reports.py:158 #: reports.py:161
msgid "Tests passed" msgid "Tests passed"
msgstr "" msgstr ""
#: reports.py:159 #: reports.py:162
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
#: reports.py:161 #: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:164
msgid "and" msgid "and"
msgstr "" 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}." msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "" 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}" msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 #: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158 #: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong." msgid "Error in falsecount, something went wrong."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80 #: features/steps/attributes_eleclasses_methods.py:84
msgid "" msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class " "For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value." "attributes {parameter} has no value."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81 #: features/steps/attributes_eleclasses_methods.py:85
msgid "" msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at" "For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: " " least one of these class attributes {parameter} has no value: "
"{falseelems}" "{falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82 #: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:108 #: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:133 #: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:31 #: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:52 #: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:127 #: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file." msgid "There are no {ifc_class} elements in the IFC file."
msgstr "" 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." msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107 #: features/steps/attributes_eleclasses_methods.py:111
msgid "" msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not " "The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}" "set: {falseelems}"
msgstr "" 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." msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132 #: features/steps/attributes_eleclasses_methods.py:138
msgid "" msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements " "The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}" "is not set: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:29 #: features/steps/attributes_psets_methods.py:31
msgid "" msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}" "All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset." " in the pset."
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:30 #: features/steps/attributes_psets_methods.py:32
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are " "The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}" "missing the property {parameter} in the pset: {falseelems}"
msgstr "" 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." msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:51 #: features/steps/geometric_detail_methods.py:55
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a " "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}" "{parameter} representation: {falseelems}"
msgstr "" 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." msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:126 #: features/steps/geometric_detail_methods.py:133
msgid "" msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements " "The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}" "have errors: {falseelems}"
msgstr "" 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 {}" msgid "We expected a schema of {} but instead got {}"
msgstr "" msgstr ""
@@ -7,131 +7,137 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2020-12-23 11:21+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: fr\n" "Language: fr\n"
"Language-Team: fr <LL@li.org>\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" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n" "Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160 #: reports.py:158
msgid "OpenBIM auditing is a feature of"
msgstr "L'audit OpenBIM auditing est une fonctionnalité de"
#: reports.py:155
msgid "en" msgid "en"
msgstr "fr" msgstr "fr"
#: reports.py:156 #: reports.py:159
msgid "Success" msgid "Success"
msgstr "Succès" msgstr "Succès"
#: reports.py:157 #: reports.py:160
msgid "Failure" msgid "Failure"
msgstr "Échec" msgstr "Échec"
#: reports.py:158 #: reports.py:161
msgid "Tests passed" msgid "Tests passed"
msgstr "Tests réussis" msgstr "Tests réussis"
#: reports.py:159 #: reports.py:162
msgid "Duration" msgid "Duration"
msgstr "Durée" 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" msgid "and"
msgstr "et" 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}." msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "" 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}" msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr "" 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." msgid "Error in falsecount, something went wrong."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80 #: features/steps/attributes_eleclasses_methods.py:84
msgid "" msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes " "For all {elemcount} {ifc_class} elements at least one of these class "
"{parameter} has no value." "attributes {parameter} has no value."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81 #: features/steps/attributes_eleclasses_methods.py:85
msgid "" msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least " "For the following {falsecount} out of {elemcount} {ifc_class} elements at"
"one of these class attributes {parameter} has no value: {falseelems}" " least one of these class attributes {parameter} has no value: "
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: "
"{falseelems}" "{falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131 #: features/steps/attributes_eleclasses_methods.py:86
msgid "The description of all {elemcount} {elemcount} elements is not set." #: 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 "" 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 "" 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}" "set: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:29 #: features/steps/attributes_eleclasses_methods.py:137
msgid "" msgid "The description of all {elemcount} {elemcount} elements is not set."
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:30 #: features/steps/attributes_eleclasses_methods.py:138
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the " "The description of {falsecount} out of {elemcount} {ifc_class} elements "
"property {parameter} in the pset: {falseelems}" "is not set: {falseelems}"
msgstr "" 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." msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:51 #: features/steps/geometric_detail_methods.py:55
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a " "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}" "{parameter} representation: {falseelems}"
msgstr "" 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." msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:126 #: features/steps/geometric_detail_methods.py:133
msgid "" msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have " "The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"errors: {falseelems}" "have errors: {falseelems}"
msgstr "" 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 {}" msgid "We expected a schema of {} but instead got {}"
msgstr "" 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. # Translations template for PROJECT.
# Copyright (C) 2020 ORGANIZATION # Copyright (C) 2021 ORGANIZATION
# This file is distributed under the same license as the PROJECT project. # This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020. # FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,122 +17,126 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n" "Generated-By: Babel 2.6.0\n"
#: reports.py:153 reports.py:160 #: reports.py:158
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
msgid "en" msgid "en"
msgstr "" msgstr ""
#: reports.py:156 #: reports.py:159
msgid "Success" msgid "Success"
msgstr "" msgstr ""
#: reports.py:157 #: reports.py:160
msgid "Failure" msgid "Failure"
msgstr "" msgstr ""
#: reports.py:158 #: reports.py:161
msgid "Tests passed" msgid "Tests passed"
msgstr "" msgstr ""
#: reports.py:159 #: reports.py:162
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
#: reports.py:161 #: reports.py:163
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:164
msgid "and" msgid "and"
msgstr "" 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}." msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr "" 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}" msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 #: features/steps/attributes_eleclasses_methods.py:43
#: features/steps/utils.py:158 #: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong." msgid "Error in falsecount, something went wrong."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80 #: features/steps/attributes_eleclasses_methods.py:84
msgid "" msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class " "For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value." "attributes {parameter} has no value."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81 #: features/steps/attributes_eleclasses_methods.py:85
msgid "" msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at" "For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: " " least one of these class attributes {parameter} has no value: "
"{falseelems}" "{falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82 #: features/steps/attributes_eleclasses_methods.py:86
#: features/steps/attributes_eleclasses_methods.py:108 #: features/steps/attributes_eleclasses_methods.py:112
#: features/steps/attributes_eleclasses_methods.py:133 #: features/steps/attributes_eleclasses_methods.py:139
#: features/steps/attributes_psets_methods.py:31 #: features/steps/attributes_psets_methods.py:33
#: features/steps/geometric_detail_methods.py:52 #: features/steps/geometric_detail_methods.py:56
#: features/steps/geometric_detail_methods.py:127 #: features/steps/geometric_detail_methods.py:134
msgid "There are no {ifc_class} elements in the IFC file." msgid "There are no {ifc_class} elements in the IFC file."
msgstr "" 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." msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107 #: features/steps/attributes_eleclasses_methods.py:111
msgid "" msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not " "The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}" "set: {falseelems}"
msgstr "" 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." msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr "" msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132 #: features/steps/attributes_eleclasses_methods.py:138
msgid "" msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements " "The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}" "is not set: {falseelems}"
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:29 #: features/steps/attributes_psets_methods.py:31
msgid "" msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}" "All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset." " in the pset."
msgstr "" msgstr ""
#: features/steps/attributes_psets_methods.py:30 #: features/steps/attributes_psets_methods.py:32
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are " "The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}" "missing the property {parameter} in the pset: {falseelems}"
msgstr "" 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." msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:51 #: features/steps/geometric_detail_methods.py:55
msgid "" msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a " "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}" "{parameter} representation: {falseelems}"
msgstr "" 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." msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr "" msgstr ""
#: features/steps/geometric_detail_methods.py:126 #: features/steps/geometric_detail_methods.py:133
msgid "" msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements " "The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}" "have errors: {falseelems}"
msgstr "" 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 {}" msgid "We expected a schema of {} but instead got {}"
msgstr "" 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( def generate_report(
adir=".", report_dir=".",
use_report_folder=True, use_report_folder=True,
report_file_name="", report_file_name="report.json",
html_template_file_path="" html_template_file_path="",
report_file=""
): ):
# TODO use far less parameter
# to be discussed with other devs
print("# Generating HTML reports now.") print("# Generating HTML reports now.")
# get locale path # get locale path
@@ -32,23 +36,22 @@ def generate_report(
if html_template_file_path: if html_template_file_path:
report_template_path = html_template_file_path report_template_path = html_template_file_path
# get report file # get report file and report dir
report_dir = adir if report_file:
if use_report_folder: report_file = report_file
report_dir = os.path.join(adir, "report") report_dir = os.path.dirname(report_file)
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)
else: else:
report_path = os.path.join(report_dir, "report.json") if use_report_folder:
# print(report_path) report_dir = os.path.join(report_dir, "report")
if not os.path.exists(report_path): report_file = os.path.join(report_dir, report_file_name)
return print("No report data was found.") # 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 # 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: for feature in report:
file_name = os.path.basename(feature["location"]).split(":")[0] file_name = os.path.basename(feature["location"]).split(":")[0]
data = { data = {
@@ -134,6 +137,10 @@ def generate_report(
switch_locale(localedir, "de") switch_locale(localedir, "de")
elif feature["keyword"] == "Fonctionnalité": elif feature["keyword"] == "Fonctionnalité":
switch_locale(localedir, "fr") switch_locale(localedir, "fr")
elif feature["keyword"] == "Funzionalità":
switch_locale(localedir, "it")
elif feature["keyword"] == "Functionaliteit":
switch_locale(localedir, "nl")
else: else:
# standard English # standard English
switch_locale(localedir, "en") switch_locale(localedir, "en")
+161 -147
View File
@@ -1,11 +1,13 @@
import behave.formatter.pretty # Needed for pyinstaller to package it import behave.formatter.pretty # Needed for pyinstaller to package it
import fileinput
import os import os
import shutil import shutil
import sys import sys
import tempfile import tempfile
import webbrowser 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 # get bimtester source code module path
@@ -26,29 +28,106 @@ def get_resource_path(relative_path):
def run_tests(args): 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 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"]: if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split()) behave_args.extend(args["advanced_arguments"].split())
elif not args["console"]:
if args["ifcfile"]:
behave_args.extend([ 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", "--format",
"json.pretty", "json.pretty",
# report file, if relative, than relative to current shell path
# next two lines are one arg
"--outfile", "--outfile",
"report/report.json" report_file,
]) ])
behave_args.extend([
"--define", return behave_args
"localedir={}".format(locale_path)
])
if args["ifcfile"]: def run_behave(behave_args):
behave_args.extend(["--define", "ifcfile={}".format(args["ifcfile"])])
if args["path"]: from json import dumps
behave_args.extend(["--define", "path={}".format(args["path"])]) print(dumps(behave_args, indent=4))
from behave.__main__ import main as behave_main
behave_main(behave_args) behave_main(behave_args)
print("# All tests are finished.") print("# All tests are finished.")
return True return True
@@ -99,41 +178,9 @@ reset_runtime()
""" """
# TODO: if the ifc file name or path contains special character def copy_intmp_tests(args={}):
# like German Umlaute behave gives an error
print("# Copy features and steps to temp.")
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
"""
from behave import __version__ as behave_version from behave import __version__ as behave_version
# https://github.com/behave/behave/issues/871 # https://github.com/behave/behave/issues/871
@@ -144,53 +191,82 @@ def run_intmp_tests(args={}):
) )
return False return False
# print(args)
# get the features_path, the dir where the feature files to test are in # get the features_path, the dir where the feature files to test are in
if "features" in args and args["features"] != "": if ("featuresdir" in args and args["featuresdir"] != ""):
the_features_path = os.path.join(args["features"], "features") is_features = True
the_features_path = os.path.join(args["featuresdir"], "features")
if not os.path.isdir(the_features_path): if not os.path.isdir(the_features_path):
print( print(
"The features directory does not exist: {}" "Error, the features directory '{}' does not exist."
.format(the_features_path) .format(the_features_path)
) )
return False return False
else: else:
# TODO assume features beside ifc thus use ifc path is_features = False
print("No features path was given.")
return False
# get ifc path and ifc filename # 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"] 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)) ifc_path = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifc_path) is False: if os.path.isdir(ifc_path) is False:
print("ifc path does not exist.") print("ifc path does not exist.")
return False 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: else:
# TODO use ifc path from feature files is_ifcfile = False
print("No ifc file was given.")
# 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 return False
# set up paths # set up paths
# a unique temp path should not be used # a unique temp path should not be used
# behave raises an ambiguous step exception # behave raises an ambiguous step exception
# run_path = tempfile.mkdtemp() # copy_base_path = tempfile.mkdtemp()
# thus use the same path on every run # thus use the same path on every run
# but delete it if exists # but delete it if exists
run_path = os.path.join(tempfile.gettempdir(), "bimtesterfc") copy_base_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(run_path): if os.path.isdir(copy_base_path):
from shutil import rmtree from shutil import rmtree
rmtree(run_path) # fails on read only files rmtree(copy_base_path) # fails on read only files
if os.path.isdir(run_path): if os.path.isdir(copy_base_path):
print("Delete former beimtester run dir {} failed".format(run_path)) print(
"Delete former beimtester run dir '{}' failed"
.format(copy_base_path)
)
return False return False
os.mkdir(run_path) os.mkdir(copy_base_path)
report_path = os.path.join(run_path, "report") copy_features_path = os.path.join(copy_base_path, "features")
copy_features_path = os.path.join(run_path, "features")
# copy features path from bimtester source code # copy features path from bimtester source code
srccode_features_path = os.path.join( srccode_features_path = os.path.join(
@@ -218,8 +294,7 @@ def run_intmp_tests(args={}):
# dirs_exist_ok=True # dirs_exist_ok=True
# ) # )
# copy feature files and replace ifcpath in feature files # copy feature files
# replaceing is IMHO better than copy the ifc file which could be 500 MB
feature_files = os.listdir(the_features_path) feature_files = os.listdir(the_features_path)
# print(feature_files) # print(feature_files)
for feature_file in 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), os.path.join(the_features_path, feature_file),
cp_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 return args, copy_base_path
# 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
def run_all(the_features_path, the_ifcfile): def run_all(args):
print("# Run all.")
# run bimtester # run bimtester
runpath = run_intmp_tests({ report_file = run_tests(args)
"features": the_features_path, print(report_file)
"ifcfile": the_ifcfile
})
print(runpath)
# check if it worked out well # check if it worked out well
if runpath is False: if report_file is False:
print("BIMTester behave tests returned False.") print("BIMTester behave tests returned False.")
return False return False
if not os.path.isdir(runpath): if not os.path.isfile(report_file):
print("runpath does not exist. This should not happen. Debug") print("Report directory does not exist. This should not happen. Debug")
return False return False
# create html report and open in webbrowser # create html report and open in webbrowser
from .reports import generate_report from .reports import generate_report
generate_report(runpath) generate_report(report_file=report_file)
# get the feature files # get the feature files
feature_files = os.listdir( feature_files = os.listdir(
os.path.join(the_features_path, "features") os.path.join(args["featuresdir"], "features")
) )
# print(feature_files) # print(feature_files)
for ff in feature_files: for ff in feature_files:
webbrowser.open(os.path.join( webbrowser.open(os.path.join(
runpath, os.path.dirname(report_file),
"report",
ff + ".html" 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 #!/usr/bin/env python3
import argparse import argparse
import os
from bimtester import clean from bimtester import clean
from bimtester import reports from bimtester import reports
from bimtester import run from bimtester import run
def show_widget(features="", ifcfile=""): def show_widget(
features="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
import sys import sys
from PySide2 import QtWidgets from PySide2 import QtWidgets
@@ -18,7 +24,12 @@ def show_widget(features="", ifcfile=""):
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
# Create and show the form # Create and show the form
form = GuiWidgetBimTester(features, ifcfile) form = GuiWidgetBimTester(
features,
ifcfile,
get_featurepath_from_ifcpath,
args
)
form.show() form.show()
# Run the main Qt loop # Run the main Qt loop
@@ -27,15 +38,76 @@ def show_widget(features="", ifcfile=""):
if __name__ == "__main__": if __name__ == "__main__":
# TODO make similar to bash commands
# use - not _ in named args
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Runs unit tests for BIM data" 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( parser.add_argument(
"-p", "-p",
"--purge", "--purge",
action="store_true", action="store_true",
help="Purge tests of deleted elements" 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( parser.add_argument(
"-r", "-r",
"--report", "--report",
@@ -49,66 +121,18 @@ if __name__ == "__main__":
help="Generate a HTML report after running the tests" help="Generate a HTML report after running the tests"
) )
parser.add_argument( parser.add_argument(
"-path", "-t",
"--path", "--copyintemprun",
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",
action="store_true", action="store_true",
help=( help=(
"Start the gui. The option t (run in temp directory) " "Copy steps and feature files into a temporary directory "
"is triggered automaticly." "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()) args = vars(parser.parse_args())
print(args) from json import dumps
print(dumps(args, indent=4))
if args["path"]: if args["path"]:
if args["feature"]: if args["feature"]:
@@ -121,9 +145,19 @@ if __name__ == "__main__":
elif args["report"]: elif args["report"]:
reports.generate_report() reports.generate_report()
elif args["gui"]: 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: else:
run.run_tests(args) run.run_tests(args)
if args["report_after_run"]: if args["report_after_run"]:
reports.generate_report() reports.generate_report()
print("# All tasks are complete :-)") print("# All tasks are complete :-)")
+1 -15
View File
@@ -266,21 +266,7 @@ endif
rm -rf dist/working rm -rf dist/working
# Provides IFCPatch functionality # 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/ && svn export https://github.com/IfcOpenShell/IfcOpenShell/trunk/src/ifcpatch
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 && sed -i "s/999999/$(VERSION)/" __init__.py cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blender28-bim-$(VERSION)-$(PLATFORM).zip ./* 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: if bpy is not None:
import bpy import bpy
import blenderbim.bim.module.root as module_root import importlib
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
from . import ui, prop, operator 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 = [ classes = [
operator.SelectClass, operator.SelectClass,
operator.SelectType, operator.SelectType,
operator.OpenUri, operator.OpenUri,
operator.SelectFeaturesDir,
operator.SelectDiffJsonFile,
operator.SelectDiffNewFile,
operator.SelectDiffOldFile,
operator.SelectDataDir, operator.SelectDataDir,
operator.SelectSchemaDir, operator.SelectSchemaDir,
operator.SelectIfcFile, operator.SelectIfcFile,
operator.ValidateIfcFile,
operator.ExportIFC, operator.ExportIFC,
operator.ImportIFC, operator.ImportIFC,
operator.ColourByClass,
operator.ColourByAttribute, operator.ColourByAttribute,
operator.ColourByPset, operator.ColourByPset,
operator.ResetObjectColours,
operator.ApproveClass,
operator.RejectClass,
operator.SelectAudited,
operator.RejectElement,
operator.SelectExternalMaterialDir, operator.SelectExternalMaterialDir,
operator.AddSweptSolid, operator.AddSweptSolid,
operator.RemoveSweptSolid, operator.RemoveSweptSolid,
@@ -54,8 +57,6 @@ if bpy is not None:
operator.SelectSweptSolidInnerCurves, operator.SelectSweptSolidInnerCurves,
operator.AssignSweptSolidExtrusion, operator.AssignSweptSolidExtrusion,
operator.SelectSweptSolidExtrusion, operator.SelectSweptSolidExtrusion,
operator.AddQto,
operator.RemoveQto,
operator.AddMaterialPset, operator.AddMaterialPset,
operator.RemoveMaterialPset, operator.RemoveMaterialPset,
operator.AddMaterialLayer, operator.AddMaterialLayer,
@@ -86,10 +87,6 @@ if bpy is not None:
operator.SelectGlobalId, operator.SelectGlobalId,
operator.SelectAttribute, operator.SelectAttribute,
operator.SelectPset, operator.SelectPset,
operator.CreateAggregate,
operator.EditAggregate,
operator.SaveAggregate,
operator.ExplodeAggregate,
operator.LoadClassification, operator.LoadClassification,
operator.AddClassification, operator.AddClassification,
operator.RemoveClassification, operator.RemoveClassification,
@@ -107,8 +104,6 @@ if bpy is not None:
operator.OpenView, operator.OpenView,
operator.OpenViewCamera, operator.OpenViewCamera,
operator.ActivateView, operator.ActivateView,
operator.ExecuteIfcDiff,
operator.VisualiseDiff,
operator.ExportClashSets, operator.ExportClashSets,
operator.ImportClashSets, operator.ImportClashSets,
operator.AddClashSet, operator.AddClashSet,
@@ -133,11 +128,6 @@ if bpy is not None:
operator.RemovePropertyTemplate, operator.RemovePropertyTemplate,
operator.AddSectionPlane, operator.AddSectionPlane,
operator.RemoveSectionPlane, operator.RemoveSectionPlane,
operator.AddCsvAttribute,
operator.RemoveCsvAttribute,
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.EyedropIfcCsv,
operator.ReloadIfcFile, operator.ReloadIfcFile,
operator.AddIfcFile, operator.AddIfcFile,
operator.RemoveIfcFile, operator.RemoveIfcFile,
@@ -148,14 +138,6 @@ if bpy is not None:
operator.AddVariable, operator.AddVariable,
operator.RemoveVariable, operator.RemoveVariable,
operator.PropagateTextData, operator.PropagateTextData,
operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal,
operator.GuessQuantity,
operator.ExecuteBIMTester,
operator.BIMTesterPurge,
operator.SelectCobieIfcFile,
operator.SelectCobieJsonFile,
operator.ExecuteIfcCobie,
operator.SelectIfcPatchInput, operator.SelectIfcPatchInput,
operator.SelectIfcPatchOutput, operator.SelectIfcPatchOutput,
operator.ExecuteIfcPatch, operator.ExecuteIfcPatch,
@@ -178,8 +160,6 @@ if bpy is not None:
operator.BuildSchedule, operator.BuildSchedule,
operator.AddScheduleToSheet, operator.AddScheduleToSheet,
operator.SetViewportShadowFromSun, operator.SetViewportShadowFromSun,
operator.SetNorthOffset,
operator.GetNorthOffset,
operator.AddPresentationLayer, operator.AddPresentationLayer,
operator.AssignPresentationLayer, operator.AssignPresentationLayer,
operator.UnassignPresentationLayer, operator.UnassignPresentationLayer,
@@ -197,17 +177,10 @@ if bpy is not None:
operator.LinkIfc, operator.LinkIfc,
operator.SnapSpacesTogether, operator.SnapSpacesTogether,
operator.CopyGrid, operator.CopyGrid,
operator.AddSectionsAnnotations,
prop.StrProperty, prop.StrProperty,
prop.Attribute, prop.Attribute,
prop.MaterialLayer,
prop.MaterialConstituent,
prop.MaterialProfile,
prop.MaterialSet,
prop.Variable, prop.Variable,
prop.Role,
prop.Address,
prop.Person,
prop.Organisation,
prop.Classification, prop.Classification,
prop.ClassificationReference, prop.ClassificationReference,
prop.ClassificationView, prop.ClassificationView,
@@ -227,8 +200,6 @@ if bpy is not None:
prop.BIMProperties, prop.BIMProperties,
prop.DocProperties, prop.DocProperties,
prop.BIMLibrary, prop.BIMLibrary,
prop.MapConversion,
prop.TargetCRS,
prop.IfcParameter, prop.IfcParameter,
prop.BoundaryCondition, prop.BoundaryCondition,
prop.PsetQto, prop.PsetQto,
@@ -245,26 +216,17 @@ if bpy is not None:
ui.BIM_PT_drawings, ui.BIM_PT_drawings,
ui.BIM_PT_schedules, ui.BIM_PT_schedules,
ui.BIM_PT_sheets, ui.BIM_PT_sheets,
ui.BIM_PT_bim,
ui.BIM_PT_psets, ui.BIM_PT_psets,
ui.BIM_PT_classifications, ui.BIM_PT_classifications,
ui.BIM_PT_document_information, ui.BIM_PT_document_information,
ui.BIM_PT_constraints, ui.BIM_PT_constraints,
ui.BIM_PT_search, ui.BIM_PT_search,
ui.BIM_PT_ifccsv,
ui.BIM_PT_ifcclash, ui.BIM_PT_ifcclash,
ui.BIM_PT_qa,
ui.BIM_PT_library, ui.BIM_PT_library,
ui.BIM_PT_gis,
ui.BIM_PT_presentation_layers, ui.BIM_PT_presentation_layers,
ui.BIM_PT_diff,
ui.BIM_PT_cobie,
ui.BIM_PT_patch, ui.BIM_PT_patch,
ui.BIM_PT_mvd, ui.BIM_PT_mvd,
ui.BIM_PT_material,
ui.BIM_PT_presentation_layer_data, ui.BIM_PT_presentation_layer_data,
ui.BIM_PT_object_material,
ui.BIM_PT_object_qto,
ui.BIM_PT_classification_references, ui.BIM_PT_classification_references,
ui.BIM_PT_documents, ui.BIM_PT_documents,
ui.BIM_PT_constraint_relations, ui.BIM_PT_constraint_relations,
@@ -288,22 +250,8 @@ if bpy is not None:
ui.BIM_ADDON_preferences, ui.BIM_ADDON_preferences,
] ]
classes.extend(module_root.classes) for module in modules.values():
classes.extend(module_aggregate.classes) classes.extend(module.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)
def menu_func_export(self, context): def menu_func_export(self, context):
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") 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.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary) 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.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.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties) bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties) bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units) bpy.types.SCENE_PT_unit.append(ui.ifc_units)
module_root.register()
module_aggregate.register() for module in modules.values():
module_attribute.register() module.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()
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler) bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
bpy.app.handlers.load_post.append(prop.toggleDecorationsOnLoad) 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) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del bpy.types.Scene.BIMProperties del bpy.types.Scene.BIMProperties
del bpy.types.Scene.DocProperties del bpy.types.Scene.DocProperties
del bpy.types.Scene.MapConversion
del bpy.types.Scene.TargetCRS
del bpy.types.Object.BIMObjectProperties 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.Material.BIMMaterialProperties
del bpy.types.Mesh.BIMMeshProperties del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Camera.BIMCameraProperties del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units) bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
module_unit.unregister()
module_type.unregister() for module in reversed(list(modules.values())):
module_style.unregister() module.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()
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler) bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
@@ -13,6 +13,7 @@ import gpu
import bgl import bgl
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
from . import helper
class BaseDecorator(): class BaseDecorator():
@@ -641,7 +642,7 @@ class LevelDecorator(BaseDecorator):
self.draw_labels(context, obj, splines) self.draw_labels(context, obj, splines)
class PlanDecorator(LevelDecorator): class PlanLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Plan Level" basename = "IfcAnnotation/Plan Level"
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
@@ -723,7 +724,7 @@ class PlanDecorator(LevelDecorator):
self.draw_label(context, text, p0, dir, gap=8, center=False) self.draw_label(context, text, p0, dir, gap=8, center=False)
class SectionDecorator(LevelDecorator): class SectionLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Section Level" basename = "IfcAnnotation/Section Level"
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
@@ -996,6 +997,108 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0) 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(): class DecorationsHandler():
decorators_classes = [ decorators_classes = [
DimensionDecorator, DimensionDecorator,
@@ -1004,10 +1107,11 @@ class DecorationsHandler():
HiddenDecorator, HiddenDecorator,
LeaderDecorator, LeaderDecorator,
MiscDecorator, MiscDecorator,
PlanDecorator, PlanLevelDecorator,
SectionDecorator, SectionLevelDecorator,
StairDecorator, StairDecorator,
BreakDecorator BreakDecorator,
SectionViewDecorator
] ]
installed = None installed = None
@@ -1031,11 +1135,9 @@ class DecorationsHandler():
self.decorators = [cls() for cls in self.decorators_classes] self.decorators = [cls() for cls in self.decorators_classes]
def __call__(self, context): def __call__(self, context):
props = context.scene.DocProperties collection, _ = helper.get_active_drawing(context.scene)
if props.active_drawing_index is None or len(props.drawings) == 0: if collection is None:
return return
drawing = props.drawings[props.active_drawing_index]
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
for decorator in self.decorators: for decorator in self.decorators:
for obj in decorator.get_objects(collection): 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) 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): def ortho_view_frame(camera, margin=0.015):
"""Calculates 2d bounding box of camera view area. """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 :type camera: bpy.types.Camera + BIMCameraProperties
:arg margin: margins, in scene units :arg margin: margins, in scene units
:type margin: float :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 aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
size = camera.ortho_scale size = camera.ortho_scale
@@ -317,7 +338,11 @@ def ortho_view_frame(camera, margin=0.015):
scale = parse_diagram_scale(camera) scale = parse_diagram_scale(camera)
xmarg = margin * scale xmarg = margin * scale
ymarg = margin * scale * aspect 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): def clip_segment(bounds, segm):
@@ -329,11 +354,11 @@ def clip_segment(bounds, segm):
""" """
# LiangBarsky algorithm # LiangBarsky algorithm
xmin, xmax, ymin, ymax = bounds xmin, xmax, ymin, ymax, _, _ = bounds
p1, p2 = segm p1, p2 = segm
def clip_side(p, q): 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: if q < 0:
return None # outside return None # outside
else: else:
@@ -368,3 +393,19 @@ def clip_segment(bounds, segm):
p2c = p1 + dlt * t2 p2c = p1 + dlt * t2
return p1c, p2c 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 multiprocessing
import zipfile import zipfile
import tempfile import tempfile
import numpy as np
from pathlib import Path from pathlib import Path
from itertools import cycle from itertools import cycle
from datetime import datetime from datetime import datetime
@@ -85,7 +86,7 @@ class MaterialCreator:
item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add() item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add()
item_id.name = str(item.id()) 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) style_name = self.get_surface_style_name(styled_item)
if not style_name: if not style_name:
@@ -130,7 +131,9 @@ class MaterialCreator:
material_to_slot[i] = slot_index material_to_slot[i] = slot_index
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]): 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) self.mesh.polygons.foreach_set("material_index", material_index)
def canonicalise_material_name(self, name): def canonicalise_material_name(self, name):
@@ -180,86 +183,31 @@ class MaterialCreator:
def create_single(self, material): def create_single(self, material):
if material.Name not in self.materials: if material.Name not in self.materials:
self.create_new_single(material) 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): 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: for layer in layer_set.MaterialLayers:
new = props.material_set.material_layers.add()
if layer.Material: if layer.Material:
if layer.Material.Name not in self.materials: if layer.Material.Name not in self.materials:
self.create_new_single(layer.Material) 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): 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: 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: if constituent.Material.Name not in self.materials:
self.create_new_single(constituent.Material) 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): 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: 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: if profile.Material.Name not in self.materials:
self.create_new_single(profile.Material) 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): 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: for material in material_list.Materials:
new = props.material_set.material_constituents.add()
if material.Name not in self.materials: if material.Name not in self.materials:
self.create_new_single(material) self.create_new_single(material)
new.material = self.materials[material.Name]
def create_new_single(self, material): def create_new_single(self, material):
self.materials[material.Name] = obj = bpy.data.materials.new(material.Name) self.materials[material.Name] = obj = bpy.data.materials.new(material.Name)
obj.BIMMaterialProperties.ifc_definition_id = int(material.id()) obj.BIMObjectProperties.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)
if not material.HasRepresentation or not material.HasRepresentation[0].Representations: if not material.HasRepresentation or not material.HasRepresentation[0].Representations:
return return
for representation in material.HasRepresentation[0].Representations: for representation in material.HasRepresentation[0].Representations:
@@ -280,7 +228,7 @@ class MaterialCreator:
if style.Name: if style.Name:
return style.Name return style.Name
return str(style.id()) 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): def parse_styled_item(self, styled_item, material):
styles = self.get_styled_item_styles(styled_item) styles = self.get_styled_item_styles(styled_item)
@@ -288,7 +236,6 @@ class MaterialCreator:
if not style.is_a("IfcSurfaceStyle"): if not style.is_a("IfcSurfaceStyle"):
continue continue
material.BIMMaterialProperties.ifc_style_id = int(style.id()) material.BIMMaterialProperties.ifc_style_id = int(style.id())
external_style = None
for surface_style in style.Styles: for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"): if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0 alpha = 1.0
@@ -301,13 +248,6 @@ class MaterialCreator:
surface_style.SurfaceColour.Blue, surface_style.SurfaceColour.Blue,
alpha, 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 # IfcPresentationStyleAssignment is deprecated as of IFC4
# However it is still widely used thanks to Revit :( # However it is still widely used thanks to Revit :(
@@ -395,8 +335,6 @@ class IfcImporter:
self.profile_code("Purge diffs") self.profile_code("Purge diffs")
self.load_existing_rooted_elements() self.load_existing_rooted_elements()
self.profile_code("Load existing rooted elements") self.profile_code("Load existing rooted elements")
self.cache_file()
self.profile_code("Caching file")
self.load_file() self.load_file()
self.profile_code("Loading file") self.profile_code("Loading file")
self.set_ifc_file() self.set_ifc_file()
@@ -406,8 +344,8 @@ class IfcImporter:
self.profile_code("Set vendor worksarounds") self.profile_code("Set vendor worksarounds")
self.calculate_unit_scale() self.calculate_unit_scale()
self.profile_code("Calculate unit scale") self.profile_code("Calculate unit scale")
self.patch_ifc() self.calculate_model_offset()
self.profile_code("Patching ifc") self.profile_code("Calculate model offset")
self.set_units() self.set_units()
self.profile_code("Set units") self.profile_code("Set units")
self.create_project() self.create_project()
@@ -434,8 +372,6 @@ class IfcImporter:
if self.ifc_import_settings.should_import_native: if self.ifc_import_settings.should_import_native:
self.parse_native_elements() self.parse_native_elements()
self.profile_code("Parsing native elements") self.profile_code("Parsing native elements")
self.create_georeferencing()
self.profile_code("Georeferencing ifc")
self.create_groups() self.create_groups()
self.profile_code("Creating groups") self.profile_code("Creating groups")
self.create_grids() self.create_grids()
@@ -443,12 +379,8 @@ class IfcImporter:
if self.ifc_import_settings.should_import_native: if self.ifc_import_settings.should_import_native:
self.create_native_products() self.create_native_products()
self.profile_code("Creating native products") self.profile_code("Creating native products")
# TODO: Deprecate after bug #682 is fixed and the new importer is stable self.create_products()
if self.ifc_import_settings.should_use_legacy: self.profile_code("Creating meshified products")
self.create_products_legacy()
else:
self.create_products()
self.profile_code("Creating meshified products")
self.relate_openings() self.relate_openings()
self.profile_code("Relating openings") self.profile_code("Relating openings")
self.place_objects_in_spatial_tree() self.place_objects_in_spatial_tree()
@@ -471,46 +403,22 @@ class IfcImporter:
self.profile_code("Create presentation layers") self.profile_code("Create presentation layers")
self.add_project_to_scene() self.add_project_to_scene()
self.profile_code("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.clean_mesh()
self.profile_code("Mesh cleaning") self.profile_code("Mesh cleaning")
def auto_set_workarounds(self): 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") applications = self.file.by_type("IfcApplication")
if not applications: if not applications:
return return
if applications[0].ApplicationIdentifier == "Revit": if "prostructures" in applications[0].ApplicationFullName.lower():
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():
self.ifc_import_settings.should_allow_non_element_aggregates = True 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): def is_element_far_away(self, element):
for site in self.file.by_type(ifc_class): try:
if ( return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location)
not site.ObjectPlacement except:
or not site.ObjectPlacement.RelativePlacement pass
or not site.ObjectPlacement.RelativePlacement.Location
):
continue
if self.is_point_far_away(site.ObjectPlacement.RelativePlacement.Location):
return True
def is_point_far_away(self, point): def is_point_far_away(self, point):
# Arbitrary threshold based on experience # Arbitrary threshold based on experience
@@ -600,94 +508,94 @@ class IfcImporter:
products.extend(self.get_products_from_shape_representation(inverse_element)) products.extend(self.get_products_from_shape_representation(inverse_element))
return products return products
def patch_ifc(self): def calculate_model_offset(self):
project = self.file.by_type("IfcProject")[0] project = self.file.by_type("IfcProject")[0]
if self.ifc_import_settings.should_ignore_site_coordinates: site = self.find_decomposed_ifc_class(project, "IfcSite")
sites = self.find_decomposed_ifc_class(project, "IfcSite") if site and self.is_element_far_away(site[0]):
if self.ifc_import_settings.should_guess_georeferencing and sites: return self.guess_georeferencing(site[0])
self.guess_georeferencing(sites[0]) building = self.find_decomposed_ifc_class(project, "IfcBuilding")
for site in sites: if building and self.is_element_far_away(building[0]):
self.patch_placement_to_origin(site) return self.guess_georeferencing(building[0])
if self.ifc_import_settings.should_ignore_building_coordinates: return self.guess_absolute_coordinate()
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()
def guess_georeferencing(self, element): def guess_georeferencing(self, element):
if not element.ObjectPlacement.is_a("IfcLocalPlacement"): if not element.ObjectPlacement.is_a("IfcLocalPlacement"):
return return
placement = element.ObjectPlacement.RelativePlacement placement = element.ObjectPlacement.RelativePlacement
bpy.context.scene.MapConversion.eastings = str(placement.Location.Coordinates[0]) props = bpy.context.scene.BIMGeoreferenceProperties
bpy.context.scene.MapConversion.northings = str(placement.Location.Coordinates[1]) props.blender_eastings = str(placement.Location.Coordinates[0])
bpy.context.scene.MapConversion.orthogonal_height = str(placement.Location.Coordinates[2]) props.blender_northings = str(placement.Location.Coordinates[1])
props.blender_orthogonal_height = str(placement.Location.Coordinates[2])
if placement.RefDirection: if placement.RefDirection:
bpy.context.scene.MapConversion.x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0]) props.blender_x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
bpy.context.scene.MapConversion.x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1] * -1) props.blender_x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1])
bpy.context.scene.MapConversion.scale = "1" props.has_blender_offset = True
props.blender_offset_type = "OBJECT_PLACEMENT"
def reset_absolute_coordinates(self): def guess_absolute_coordinate(self):
# 12D can have some funky coordinates out of any sensible range. This # Civil BIM applications like to work in absolute coordinates, where the ObjectPlacement is 0,0,0 but each
# method will not work all the time, but will catch most issues. # 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 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: try:
point_lists = self.file.by_type("IfcCartesianPointList3D") point_lists = self.file.by_type("IfcCartesianPointList3D")
except: except:
# IFC2X3 does not have IfcCartesianPointList3D # IFC2X3 does not have IfcCartesianPointList3D
point_lists = [] point_lists = []
for point_list in 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): for i, point in enumerate(point_list.CoordList):
if len(point) == 2 or not self.is_point_far_away(point): if len(point) == 3 and self.is_point_far_away(point):
coord_list[i] = point return point[0]
continue # offset_point = (point[0], point[1], point[2])
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
for point in self.file.by_type("IfcCartesianPoint"): for point in self.file.by_type("IfcCartesianPoint"):
if len(point.Coordinates) == 2 or not self.is_point_far_away(point): elements_checked += 1
continue if elements_checked > element_checking_threshold:
if not offset_point: return
offset_point = (point.Coordinates[0], point.Coordinates[1], point.Coordinates[2]) if len(point.Coordinates) == 3 and self.is_point_far_away(point):
self.ifc_import_settings.logger.info("Resetting absolute coordinates by %s", point) return point[0]
point.Coordinates = (
point.Coordinates[0] - offset_point[0], def apply_blender_offset_to_matrix(self, matrix):
point.Coordinates[1] - offset_point[1], props = bpy.context.scene.BIMGeoreferenceProperties
point.Coordinates[2] - offset_point[2], 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 return mathutils.Matrix(
if self.file.wrapped_data.schema == "IFC2X3": ifcopenshell.util.geolocation.global2local(
properties = [ matrix,
self.file.createIfcPropertySingleValue( float(props.blender_eastings) * self.unit_scale,
"Eastings", None, self.file.createIfcLengthMeasure(offset_point[0]) float(props.blender_northings) * self.unit_scale,
), float(props.blender_orthogonal_height) * self.unit_scale,
self.file.createIfcPropertySingleValue( float(props.blender_x_axis_abscissa),
"Northings", None, self.file.createIfcLengthMeasure(offset_point[1]) float(props.blender_x_axis_ordinate),
), ).tolist()
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
) )
self.file.createIfcRelDefinesByProperties( return mathutils.Matrix(matrix.tolist())
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])
def find_decomposed_ifc_class(self, element, ifc_class): def find_decomposed_ifc_class(self, element, ifc_class):
results = [] results = []
@@ -701,60 +609,6 @@ class IfcImporter:
results.extend(self.find_decomposed_ifc_class(part, ifc_class)) results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results 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): def create_groups(self):
group_collection = None group_collection = None
for collection in self.project["blender"].children: for collection in self.project["blender"].children:
@@ -773,7 +627,6 @@ class IfcImporter:
else: else:
obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None) obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None)
obj.BIMObjectProperties.ifc_definition_id = element.id() obj.BIMObjectProperties.ifc_definition_id = element.id()
self.add_element_attributes(element, obj.BIMObjectProperties)
group_collection.objects.link(obj) group_collection.objects.link(obj)
self.groups[element.GlobalId] = {"ifc": element, "blender": 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 = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id() obj.BIMObjectProperties.ifc_definition_id = element.id()
obj.matrix_world = matrix_world obj.matrix_world = matrix_world
self.add_element_attributes(axis, obj.BIMObjectProperties)
grid.objects.link(obj) grid.objects.link(obj)
def create_type_products(self): def create_type_products(self):
@@ -863,11 +715,6 @@ class IfcImporter:
): ):
return representation_map 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): def create_native_products(self):
if not self.native_elements: if not self.native_elements:
return return
@@ -962,20 +809,19 @@ class IfcImporter:
if shape: if shape:
m = shape.transformation.matrix.data m = shape.transformation.matrix.data
mat = mathutils.Matrix( # We use numpy here because Blender mathutils.Matrix is not accurate enough
([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]) 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 = self.apply_blender_offset_to_matrix(mat)
obj.matrix_world = mat
self.material_creator.create(element, obj, mesh) self.material_creator.create(element, obj, mesh)
elif hasattr(element, "ObjectPlacement"): 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_representation_items(element, obj)
self.add_element_classifications(element, obj) self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj) self.add_element_document_relations(element, obj)
self.add_opening_relation(element, obj) self.add_opening_relation(element, obj)
self.add_product_definitions(element, obj)
self.added_data[element.GlobalId] = obj self.added_data[element.GlobalId] = obj
if element.is_a("IfcOpeningElement"): if element.is_a("IfcOpeningElement"):
@@ -1336,7 +1182,7 @@ class IfcImporter:
obj = self.added_data[product.GlobalId] obj = self.added_data[product.GlobalId]
obj.data.BIMMeshProperties.presentation_layer_index = layer_index obj.data.BIMMeshProperties.presentation_layer_index = layer_index
except: except:
pass # Occurs for example in opening elements or exclusions pass # Occurs for example in opening elements or exclusions
def clean_mesh(self): def clean_mesh(self):
obj = None obj = None
@@ -1355,64 +1201,6 @@ class IfcImporter:
bpy.ops.mesh.normals_make_consistent(context_override) bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(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): def add_opening_relation(self, element, obj):
if not element.is_a("IfcOpeningElement"): if not element.is_a("IfcOpeningElement"):
return return
@@ -1429,15 +1217,6 @@ class IfcImporter:
with open(self.ifc_import_settings.diff_file, "r") as file: with open(self.ifc_import_settings.diff_file, "r") as file:
self.diff = json.load(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): def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file) self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
extension = self.ifc_import_settings.input_file.split(".")[-1] extension = self.ifc_import_settings.input_file.split(".")[-1]
@@ -1708,7 +1487,6 @@ class IfcImporter:
self.add_element_classifications(element, obj) self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj) self.add_element_document_relations(element, obj)
self.add_product_definitions(element, obj)
self.aggregates[element.GlobalId] = obj self.aggregates[element.GlobalId] = obj
self.aggregate_collections[rel_aggregate.id()] = collection self.aggregate_collections[rel_aggregate.id()] = collection
@@ -1731,47 +1509,6 @@ class IfcImporter:
objects_to_purge.append(obj) objects_to_purge.append(obj)
bpy.ops.object.delete({"selected_objects": objects_to_purge}) 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): def add_element_document_relations(self, element, obj):
for association in element.HasAssociations: for association in element.HasAssociations:
if association.is_a("IfcRelAssociatesDocument"): if association.is_a("IfcRelAssociatesDocument"):
@@ -1812,7 +1549,7 @@ class IfcImporter:
return self.place_object_in_spatial_tree(container, obj) return self.place_object_in_spatial_tree(container, obj)
elif element.is_a("IfcGrid"): elif element.is_a("IfcGrid"):
grid_collection = bpy.data.collections.get(obj.name) 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) self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
grid_collection.objects.link(obj) grid_collection.objects.link(obj)
else: else:
@@ -1852,21 +1589,6 @@ class IfcImporter:
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element) self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
bpy.context.scene.collection.objects.link(obj) 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): def cast_edge_case_attribute(self, ifc_class, key, value):
if key == "RefLatitude" or key == "RefLongitude": if key == "RefLatitude" or key == "RefLongitude":
return ifcopenshell.util.geolocation.dms2dd(*value) return ifcopenshell.util.geolocation.dms2dd(*value)
@@ -1904,38 +1626,11 @@ class IfcImporter:
return self.get_referenced_source_name(element.ReferencedSource) return self.get_referenced_source_name(element.ReferencedSource)
def get_element_matrix(self, element, mesh_name=None): def get_element_matrix(self, element, mesh_name=None):
element_matrix = self.get_local_placement(element.ObjectPlacement) result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
if mesh_name: result[1][3] *= self.unit_scale
# Blender supports reusing a mesh with a different transformation result[2][3] *= self.unit_scale
# applied at the object level. In contrast, IFC supports reusing a mesh return result
# 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
def get_body_representations(self, representations, matrix=None): def get_body_representations(self, representations, matrix=None):
if matrix is None: if matrix is None:
@@ -2005,11 +1700,29 @@ class IfcImporter:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0])) representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else: else:
representation_id = int(re.sub(r"\D", "", representation_id)) representation_id = int(re.sub(r"\D", "", representation_id))
mesh = bpy.data.meshes.new("{}/{}".format( mesh = bpy.data.meshes.new(
self.file.by_id(representation_id).ContextOfItems.id(), geometry.id)) "{}/{}".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: if geometry.faces:
num_vertices = len(geometry.verts) // 3 num_vertices = len(verts) // 3
total_faces = len(geometry.faces) total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3) loop_start = range(0, total_faces, 3)
num_loops = 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 # Potentially, there is a smarter way to do this. See #1047
v_index = cycle((0, 1, 2)) v_index = cycle((0, 1, 2))
verts = [ 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) mesh.vertices.foreach_set("co", verts)
else: else:
mesh.vertices.foreach_set("co", geometry.verts) mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices) mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces) mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops) mesh.polygons.add(num_loops)
@@ -2034,7 +1747,7 @@ class IfcImporter:
mesh.update() mesh.update()
else: else:
e = geometry.edges 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)] 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)] edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, []) mesh.from_pydata(vertices, edges, [])
@@ -2051,6 +1764,7 @@ class IfcImporter:
except: except:
self.ifc_import_settings.logger.error("Could not create mesh for %s", element) self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
import traceback import traceback
print(traceback.format_exc()) print(traceback.format_exc())
def create_curve(self, geometry): def create_curve(self, geometry):
@@ -2155,7 +1869,6 @@ class IfcImportSettings:
self.should_auto_set_workarounds = True self.should_auto_set_workarounds = True
self.should_ignore_site_coordinates = False self.should_ignore_site_coordinates = False
self.should_ignore_building_coordinates = False self.should_ignore_building_coordinates = False
self.should_reset_absolute_coordinates = False
self.should_merge_materials_by_colour = False self.should_merge_materials_by_colour = False
self.should_import_type_representations = False self.should_import_type_representations = False
self.should_import_curves = False self.should_import_curves = False
@@ -2164,7 +1877,6 @@ class IfcImportSettings:
self.should_use_cpu_multiprocessing = False self.should_use_cpu_multiprocessing = False
self.should_import_with_profiling = False self.should_import_with_profiling = False
self.should_import_native = False self.should_import_native = False
self.should_use_legacy = False
self.should_merge_aggregates = False self.should_merge_aggregates = False
self.should_merge_by_class = False self.should_merge_by_class = False
self.should_merge_by_material = False self.should_merge_by_material = False
@@ -2181,10 +1893,6 @@ class IfcImportSettings:
settings.diff_file = scene_bim.diff_json_file settings.diff_file = scene_bim.diff_json_file
settings.ifc_import_filter = scene_bim.ifc_import_filter settings.ifc_import_filter = scene_bim.ifc_import_filter
settings.ifc_selector = scene_bim.ifc_selector 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_type_representations = scene_bim.import_should_import_type_representations
settings.should_import_curves = scene_bim.import_should_import_curves settings.should_import_curves = scene_bim.import_should_import_curves
settings.should_import_opening_elements = scene_bim.import_should_import_opening_elements 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_with_profiling = scene_bim.import_should_import_with_profiling
settings.should_import_native = scene_bim.import_should_import_native settings.should_import_native = scene_bim.import_should_import_native
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_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_import_aggregates = scene_bim.import_should_import_aggregates
settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates
settings.should_merge_by_class = scene_bim.import_should_merge_by_class 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