merge master v6.0

This commit is contained in:
admin
2021-01-11 11:39:36 +08:00
parent 699742880e
commit 555a2ce79e
40 changed files with 1189 additions and 25605 deletions
@@ -1,12 +1,70 @@
import os
from behave.model import Scenario from behave.model import Scenario
from logfile import create_logfile
from logfile import append_logfile
from zoom_smart_view import append_zoom_smartview
from zoom_smart_view import create_zoom_smartview
this_path = os.path.dirname(os.path.realpath(__file__))
def before_all(context): def before_all(context):
# get from userdata
userdata = context.config.userdata userdata = context.config.userdata
context.ifcbasename = userdata["ifcbasename"] context.ifcbasename = userdata["ifcbasename"]
context.localedir = userdata.get("localedir") context.localedir = userdata.get("localedir")
# 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
continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True) continue_after_failed = userdata.getbool(
"runner.continue_after_failed_step", True
)
Scenario.continue_after_failed_step = continue_after_failed Scenario.continue_after_failed_step = continue_after_failed
# keep out path
context.outpath = os.path.join(this_path, "..")
# since bimtesterfc directory in tmp is removed on every run
# neither log file nor sm file does need to be explicit removed first
# set up log file
context.thelogfile = os.path.join(
context.outpath,
context.ifcbasename + ".log"
)
create_logfile(
context.thelogfile,
context.ifcbasename,
)
# set up smart view file
context.smview_file = os.path.join(
context.outpath,
context.ifcbasename + ".bcsv"
)
create_zoom_smartview(
context.smview_file,
context.ifcbasename,
)
def after_step(context, step):
if step.status == "failed":
# append log file
append_logfile(context, step)
# extend smart view
if hasattr(context, "falseguids"):
# print(context.falseguids)
append_zoom_smartview(
context.smview_file,
step.name,
context.falseguids
)
@@ -1,36 +1,49 @@
from behave import step from behave import step
from attributes_eleclasses_methods import all_element_attribs_have_a_value import attributes_eleclasses_methods as aem
from attributes_eleclasses_methods import name_has_a_value
from attributes_eleclasses_methods import description_has_a_value
from attributes_eleclasses_methods import no_element_class_ele
from utils import assert_elements from utils import assert_elements
from utils import IfcFile from utils import IfcFile
from utils import switch_locale from utils import switch_locale
the_lang = "en"
@step("there are no {ifc_class} elements because {reason}") @step("there are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason): def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, "en") switch_locale(context.localedir, the_lang)
no_element_class_ele(context, ifc_class, reason) aem.no_eleclass_because_reason(
context,
ifc_class,
reason
)
@step('all {ifc_class} elements class attributes have a value') @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, "en") switch_locale(context.localedir, the_lang)
all_element_attribs_have_a_value(context, ifc_class) aem.eleclass_have_class_attributes_with_a_value(
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, "en") switch_locale(context.localedir, the_lang)
name_has_a_value(context, ifc_class) aem.eleclass_has_name_with_a_value(
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, "en") switch_locale(context.localedir, the_lang)
description_has_a_value(context, ifc_class) aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
@step('all {ifc_class} elements have a name matching the pattern "{pattern}"') @step('all {ifc_class} elements have a name matching the pattern "{pattern}"')
@@ -4,7 +4,9 @@ from utils import assert_elements
from utils import IfcFile from utils import IfcFile
def no_element_class_ele(context, ifc_class, reason): def no_eleclass_because_reason(
context, ifc_class, reason
):
context.falseelems = [] context.falseelems = []
context.falseguids = [] context.falseguids = []
@@ -41,7 +43,9 @@ def no_element_class_ele(context, ifc_class, reason):
assert False, _("Error in falsecount, something went wrong.") assert False, _("Error in falsecount, something went wrong.")
def all_element_attribs_have_a_value(context, ifc_class): def eleclass_have_class_attributes_with_a_value(
context, ifc_class
):
from ifcopenshell.ifcopenshell_wrapper import schema_by_name from ifcopenshell.ifcopenshell_wrapper import schema_by_name
# schema = schema_by_name("IFC2X3") # schema = schema_by_name("IFC2X3")
@@ -84,7 +88,7 @@ def all_element_attribs_have_a_value(context, ifc_class):
) )
def name_has_a_value(context, ifc_class): def eleclass_has_name_with_a_value(context, ifc_class):
context.falseelems = [] context.falseelems = []
context.falseguids = [] context.falseguids = []
@@ -109,7 +113,9 @@ def name_has_a_value(context, ifc_class):
) )
def description_has_a_value(context, ifc_class): def eleclass_has_description_with_a_value(
context, ifc_class
):
context.falseelems = [] context.falseelems = []
context.falseguids = [] context.falseguids = []
@@ -1,15 +1,23 @@
from behave import step from behave import step
from attributes_psets_methods import all_eleclass_elements_have_prop_in_pset import attributes_psets_methods as apm
from utils import assert_elements from utils import assert_elements
from utils import IfcFile from utils import IfcFile
from utils import switch_locale from utils import switch_locale
the_lang = "en"
@step("all {ifc_class} elements have an {aproperty} property in the {pset} pset") @step("all {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset): def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, "en") switch_locale(context.localedir, the_lang)
all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset) apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
@@ -4,7 +4,9 @@ from utils import assert_elements
from utils import IfcFile from utils import IfcFile
def all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset): def eleclass_has_property_in_pset(
context, ifc_class, aproperty, pset
):
context.falseelems = [] context.falseelems = []
context.falseguids = [] context.falseguids = []
@@ -1,11 +1,14 @@
from behave import step from behave import step
from geometric_detail_methods import class_geometric_representation import geometric_detail_methods as gdm
from utils import assert_elements from utils import assert_elements
from utils import IfcFile from utils import IfcFile
from utils import switch_locale from utils import switch_locale
the_lang = "en"
@step("All elements must be under {number} polygons") @step("All elements must be under {number} polygons")
def step_impl(context, number): def step_impl(context, number):
number = int(number) number = int(number)
@@ -36,5 +39,9 @@ def step_impl(context, number):
@step("all {ifc_class} elements have an {representation_class} representation") @step("all {ifc_class} elements have an {representation_class} representation")
def step_impl(context, ifc_class, representation_class): def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, "en") switch_locale(context.localedir, the_lang)
class_geometric_representation(context, ifc_class, representation_class) gdm.eleclass_has_geometric_representation_of_specific_class(
context,
ifc_class,
representation_class
)
@@ -1,10 +1,17 @@
from behave import step from behave import step
from geometric_detail_methods import class_geometric_representation import geometric_detail_methods as gdm
from utils import switch_locale from utils import switch_locale
the_lang = "de"
@step("Alle {ifc_class} Bauteile müssen eine geometrische Repräsentation der Klasse {representation_class} verwenden") @step("Alle {ifc_class} Bauteile müssen eine geometrische Repräsentation der Klasse {representation_class} verwenden")
def step_impl(context, ifc_class, representation_class): def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, "de") switch_locale(context.localedir, the_lang)
class_geometric_representation(context, ifc_class, representation_class) gdm.eleclass_has_geometric_representation_of_specific_class(
context,
ifc_class,
representation_class
)
@@ -3,7 +3,11 @@ from utils import assert_elements
from utils import IfcFile from utils import IfcFile
def class_geometric_representation(context, ifc_class, representation_class): def eleclass_has_geometric_representation_of_specific_class(
context,
ifc_class,
representation_class
):
def is_item_a_representation(item, representation): def is_item_a_representation(item, representation):
if "/" in representation: if "/" in representation:
@@ -6,6 +6,10 @@ from ifcdata_methods import assert_schema
from utils import IfcFile from utils import IfcFile
from utils import switch_locale from utils import switch_locale
the_lang = "en"
@step('The IFC schema "{schema}" must be provided') @step('The IFC schema "{schema}" must be provided')
def step_impl(context, schema): def step_impl(context, schema):
try: try:
@@ -15,6 +19,7 @@ def step_impl(context, schema):
except: except:
assert False, f"The schema {schema} could not be loaded" assert False, f"The schema {schema} could not be loaded"
@step('The IFC file "{file}" must be provided') @step('The IFC file "{file}" must be provided')
def step_impl(context, file): def step_impl(context, file):
try: try:
@@ -22,6 +27,7 @@ def step_impl(context, file):
except: except:
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: try:
@@ -29,6 +35,7 @@ def step_impl(context):
except: except:
assert False, f"The IFC {context.config.userdata.get('ifcfile')} file could not be loaded" 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')
def step_impl(context): def step_impl(context):
try: try:
@@ -36,9 +43,10 @@ def step_impl(context):
except: except:
assert False, f"The path {context.config.userdata.get('path')} could not be loaded" assert False, f"The path {context.config.userdata.get('path')} could not be loaded"
@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, "en") switch_locale(context.localedir, the_lang)
assert_schema(context, schema) assert_schema(context, schema)
@@ -3,7 +3,11 @@ from behave import step
from ifcdata_methods import assert_schema from ifcdata_methods import assert_schema
from utils import switch_locale from utils import switch_locale
the_lang = "de"
@step("Die IFC Daten müssen das {schema} Schema benutzen") @step("Die IFC Daten müssen das {schema} Schema benutzen")
def step_impl(context, schema): def step_impl(context, schema):
switch_locale(context.localedir, "de") switch_locale(context.localedir, the_lang)
assert_schema(context, schema) assert_schema(context, schema)
@@ -3,7 +3,11 @@ from behave import step
from ifcdata_methods import assert_schema from ifcdata_methods import assert_schema
from utils import switch_locale from utils import switch_locale
the_lang = "fr"
@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, "fr") switch_locale(context.localedir, the_lang)
assert_schema(context, schema) assert_schema(context, schema)
@@ -1,5 +1,5 @@
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import numpy as np import ifcopenshell.util.placement
from behave import step from behave import step
from utils import assert_number from utils import assert_number
@@ -7,31 +7,6 @@ from utils import assert_type
from utils import IfcFile from utils import IfcFile
def a2p(o, z, x):
y = np.cross(z, x)
r = np.eye(4)
r[:-1, :-1] = x, y, z
r[-1, :-1] = o
return r.T
def get_axis2placement(plc):
z = np.array(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1))
x = np.array(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0))
o = plc.Location.Coordinates
return a2p(o, z, x)
def get_local_placement(plc):
if plc is None:
return np.eye(4)
if plc.PlacementRelTo is None:
parent = np.eye(4)
else:
parent = get_local_placement(plc.PlacementRelTo)
return np.dot(get_axis2placement(plc.RelativePlacement), parent)
def get_decimal_points(value): def get_decimal_points(value):
try: try:
return len(value.split(".")[1]) return len(value.split(".")[1])
@@ -83,7 +58,7 @@ def step_impl(context, guid, easting, northing, elevation):
element = IfcFile.by_guid(guid) element = IfcFile.by_guid(guid)
if not element.ObjectPlacement: if not element.ObjectPlacement:
assert False, "The element does not have an object placement: {}".format(element) assert False, "The element does not have an object placement: {}".format(element)
m = get_local_placement(element.ObjectPlacement) m = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
e, n, h = ifcopenshell.util.geolocation.xyz2enh( e, n, h = ifcopenshell.util.geolocation.xyz2enh(
m[0][3], m[0][3],
m[1][3], m[1][3],
@@ -110,7 +85,7 @@ def step_impl(context, guid, x, y, z):
element = IfcFile.by_guid(guid) element = IfcFile.by_guid(guid)
if not element.ObjectPlacement: if not element.ObjectPlacement:
assert False, "The element does not have an object placement: {}".format(element) assert False, "The element does not have an object placement: {}".format(element)
m = get_local_placement(element.ObjectPlacement) m = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
element_x = round(m[0][3], get_decimal_points(x)) element_x = round(m[0][3], get_decimal_points(x))
element_y = round(m[1][3], get_decimal_points(y)) element_y = round(m[1][3], get_decimal_points(y))
element_z = round(m[2][3], get_decimal_points(z)) element_z = round(m[2][3], get_decimal_points(z))
+2 -2
View File
@@ -14,7 +14,7 @@ def generate_report(
html_template_file_path="" html_template_file_path=""
): ):
# print("# Generating HTML reports now.") print("# Generating HTML reports now.")
# get locale path # get locale path
localedir = os.path.join( localedir = os.path.join(
@@ -150,7 +150,7 @@ def generate_report(
def get_html_template_strings(): def get_html_template_strings():
print(_("OpenBIM auditing is a feature of"))
return { return {
"tr_lang": _("en"), "tr_lang": _("en"),
"tr_success": _("Success"), "tr_success": _("Success"),
+38 -36
View File
@@ -144,13 +144,13 @@ def run_intmp_tests(args={}):
) )
return False return False
# get the features_path, the feature files where the tests are in # get the features_path, the dir where the feature files to test are in
if "features" in args and args["features"] != "": if "features" in args and args["features"] != "":
features_path = os.path.join(args["features"], "features") the_features_path = os.path.join(args["features"], "features")
if not os.path.isdir(features_path): if not os.path.isdir(the_features_path):
print( print(
"The features directory does not exist: {}" "The features directory does not exist: {}"
.format(features_path) .format(the_features_path)
) )
return False return False
else: else:
@@ -191,23 +191,47 @@ def run_intmp_tests(args={}):
os.mkdir(run_path) os.mkdir(run_path)
report_path = os.path.join(run_path, "report") report_path = os.path.join(run_path, "report")
copy_features_path = os.path.join(run_path, "features") copy_features_path = os.path.join(run_path, "features")
copy_steps_path = os.path.join(copy_features_path, "steps")
# copy features path from bimtester source code
srccode_features_path = os.path.join(
bimtester_path,
"features",
)
# print(srccode_features_path)
if os.path.exists(srccode_features_path):
shutil.copytree(srccode_features_path, copy_features_path)
else:
print(
"Bimtester source code features directory {} not found."
.format(srccode_features_path)
)
return False
# copy features files # copy features files
# print(features_path) # print(the_features_path)
# print(copy_features_path) # print(copy_features_path)
if os.path.exists(features_path): # parameter dirs_exist_ok=True, from py 3.8
shutil.copytree(features_path, copy_features_path) # if os.path.exists(the_features_path):
# shutil.copytree(
# the_features_path,
# copy_features_path,
# dirs_exist_ok=True
# )
# replace ifcpath in feature files # copy feature files and replace ifcpath in feature files
# IMHO better than copy the ifc file which could be 500 MB # replaceing is IMHO better than copy the ifc file which could be 500 MB
feature_files = os.listdir(copy_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:
feature_file = os.path.join(copy_features_path, feature_file) cp_feature_file = os.path.join(copy_features_path, feature_file)
# print(feature_file) # print(feature_file)
# copy file
shutil.copyfile(
os.path.join(the_features_path, feature_file),
cp_feature_file
)
# search the line # search the line
ff = open(feature_file, "r") ff = open(cp_feature_file, "r")
lines = ff.readlines() lines = ff.readlines()
ff.close() ff.close()
theline = "" theline = ""
@@ -228,32 +252,10 @@ def run_intmp_tests(args={}):
# replace the line # replace the line
if newifcline != "": if newifcline != "":
# https://stackoverflow.com/a/290494 # https://stackoverflow.com/a/290494
for line in fileinput.input(feature_file, inplace=True): for line in fileinput.input(cp_feature_file, inplace=True):
# the print replaces the line in the file # the print replaces the line in the file
print(line.replace(theline, newifcline), end="") print(line.replace(theline, newifcline), end="")
# copy step files and environment file
steps_path = os.path.join(
bimtester_path,
"features",
"steps"
)
# print(steps_path)
# print(copy_steps_path)
if os.path.exists(steps_path):
shutil.copytree(steps_path, copy_steps_path)
environment_file = os.path.join(
bimtester_path,
"features",
"environment.py"
)
if os.path.isfile(environment_file):
shutil.copyfile(
environment_file,
os.path.join(copy_features_path, "environment.py")
)
# get advanced args # get advanced args
# print to console from inside step files, add "--no-capture" flag # print to console from inside step files, add "--no-capture" flag
# https://github.com/behave/behave/issues/346 # https://github.com/behave/behave/issues/346
+55 -51
View File
@@ -5,15 +5,25 @@ bpy = sys.modules.get("bpy")
if bpy is not None: if bpy is not None:
import bpy import bpy
import blenderbim.bim.module.covetool as module_covetool import blenderbim.bim.module.root as module_root
import blenderbim.bim.module.model as module_model 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.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
classes = [ classes = [
operator.ReassignClass,
operator.AssignClass,
operator.UnassignClass,
operator.SelectClass, operator.SelectClass,
operator.SelectType, operator.SelectType,
operator.OpenUri, operator.OpenUri,
@@ -27,7 +37,6 @@ if bpy is not None:
operator.ValidateIfcFile, operator.ValidateIfcFile,
operator.ExportIFC, operator.ExportIFC,
operator.ImportIFC, operator.ImportIFC,
operator.ProfileImportIFC,
operator.ColourByClass, operator.ColourByClass,
operator.ColourByAttribute, operator.ColourByAttribute,
operator.ColourByPset, operator.ColourByPset,
@@ -45,8 +54,6 @@ if bpy is not None:
operator.SelectSweptSolidInnerCurves, operator.SelectSweptSolidInnerCurves,
operator.AssignSweptSolidExtrusion, operator.AssignSweptSolidExtrusion,
operator.SelectSweptSolidExtrusion, operator.SelectSweptSolidExtrusion,
operator.AddPset,
operator.RemovePset,
operator.AddQto, operator.AddQto,
operator.RemoveQto, operator.RemoveQto,
operator.AddMaterialPset, operator.AddMaterialPset,
@@ -65,18 +72,6 @@ if bpy is not None:
operator.AssignConstraint, operator.AssignConstraint,
operator.UnassignConstraint, operator.UnassignConstraint,
operator.RemoveObjectConstraint, operator.RemoveObjectConstraint,
operator.AddPerson,
operator.RemovePerson,
operator.AddPersonRole,
operator.RemovePersonRole,
operator.AddPersonAddress,
operator.RemovePersonAddress,
operator.AddOrganisation,
operator.RemoveOrganisation,
operator.AddOrganisationRole,
operator.RemoveOrganisationRole,
operator.AddOrganisationAddress,
operator.RemoveOrganisationAddress,
operator.AddDocumentInformation, operator.AddDocumentInformation,
operator.RemoveDocumentInformation, operator.RemoveDocumentInformation,
operator.AssignDocumentInformation, operator.AssignDocumentInformation,
@@ -86,11 +81,8 @@ if bpy is not None:
operator.UnassignDocumentReference, operator.UnassignDocumentReference,
operator.RemoveObjectDocumentReference, operator.RemoveObjectDocumentReference,
operator.GenerateGlobalId, operator.GenerateGlobalId,
operator.AddAttribute,
operator.RemoveAttribute,
operator.AddMaterialAttribute, operator.AddMaterialAttribute,
operator.RemoveMaterialAttribute, operator.RemoveMaterialAttribute,
operator.QuickProjectSetup,
operator.SelectGlobalId, operator.SelectGlobalId,
operator.SelectAttribute, operator.SelectAttribute,
operator.SelectPset, operator.SelectPset,
@@ -107,8 +99,6 @@ if bpy is not None:
operator.FetchLibraryInformation, operator.FetchLibraryInformation,
operator.FetchExternalMaterial, operator.FetchExternalMaterial,
operator.FetchObjectPassport, operator.FetchObjectPassport,
operator.AddSubcontext,
operator.RemoveSubcontext,
operator.CutSection, operator.CutSection,
operator.AddSheet, operator.AddSheet,
operator.OpenSheet, operator.OpenSheet,
@@ -133,8 +123,6 @@ if bpy is not None:
operator.SmartClashGroup, operator.SmartClashGroup,
operator.SelectSmartGroup, operator.SelectSmartGroup,
operator.LoadSmartGroupsForActiveClashSet, operator.LoadSmartGroupsForActiveClashSet,
operator.SwitchContext,
operator.RemoveContext,
operator.OpenUpstream, operator.OpenUpstream,
operator.BIM_OT_ChangeClassificationLevel, operator.BIM_OT_ChangeClassificationLevel,
operator.AddPropertySetTemplate, operator.AddPropertySetTemplate,
@@ -151,7 +139,6 @@ if bpy is not None:
operator.ImportIfcCsv, operator.ImportIfcCsv,
operator.EyedropIfcCsv, operator.EyedropIfcCsv,
operator.ReloadIfcFile, operator.ReloadIfcFile,
operator.SelectSimilarType,
operator.AddIfcFile, operator.AddIfcFile,
operator.RemoveIfcFile, operator.RemoveIfcFile,
operator.SelectDocIfcFile, operator.SelectDocIfcFile,
@@ -161,7 +148,6 @@ if bpy is not None:
operator.AddVariable, operator.AddVariable,
operator.RemoveVariable, operator.RemoveVariable,
operator.PropagateTextData, operator.PropagateTextData,
operator.PushRepresentation,
operator.ConvertLocalToGlobal, operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal, operator.ConvertGlobalToLocal,
operator.GuessQuantity, operator.GuessQuantity,
@@ -203,15 +189,7 @@ if bpy is not None:
operator.RemoveDrawingStyleAttribute, operator.RemoveDrawingStyleAttribute,
operator.CopyPropertyToSelection, operator.CopyPropertyToSelection,
operator.CopyAttributeToSelection, operator.CopyAttributeToSelection,
operator.CreateShapeFromStepId,
operator.SelectHighPolygonMeshes,
operator.InspectFromStepId,
operator.InspectFromObject,
operator.RewindInspector,
operator.RefreshDrawingList, operator.RefreshDrawingList,
operator.GetRepresentationIfcParameters,
operator.BakeParametricGeometry,
operator.UpdateIfcRepresentation,
operator.SetBlenderClashSetA, operator.SetBlenderClashSetA,
operator.SetBlenderClashSetB, operator.SetBlenderClashSetB,
operator.ExecuteBlenderClash, operator.ExecuteBlenderClash,
@@ -245,11 +223,8 @@ if bpy is not None:
prop.Schedule, prop.Schedule,
prop.DrawingStyle, prop.DrawingStyle,
prop.Sheet, prop.Sheet,
prop.Subcontext,
prop.Representation,
prop.PresentationLayer, prop.PresentationLayer,
prop.BIMProperties, prop.BIMProperties,
prop.BIMDebugProperties,
prop.DocProperties, prop.DocProperties,
prop.BIMLibrary, prop.BIMLibrary,
prop.MapConversion, prop.MapConversion,
@@ -278,10 +253,6 @@ if bpy is not None:
ui.BIM_PT_search, ui.BIM_PT_search,
ui.BIM_PT_ifccsv, ui.BIM_PT_ifccsv,
ui.BIM_PT_ifcclash, ui.BIM_PT_ifcclash,
ui.BIM_PT_owner,
ui.BIM_PT_people,
ui.BIM_PT_organisations,
ui.BIM_PT_context,
ui.BIM_PT_qa, ui.BIM_PT_qa,
ui.BIM_PT_library, ui.BIM_PT_library,
ui.BIM_PT_gis, ui.BIM_PT_gis,
@@ -290,15 +261,10 @@ if bpy is not None:
ui.BIM_PT_cobie, ui.BIM_PT_cobie,
ui.BIM_PT_patch, ui.BIM_PT_patch,
ui.BIM_PT_mvd, ui.BIM_PT_mvd,
ui.BIM_PT_debug,
ui.BIM_PT_material, ui.BIM_PT_material,
ui.BIM_PT_mesh,
ui.BIM_PT_presentation_layer_data, ui.BIM_PT_presentation_layer_data,
ui.BIM_PT_object,
ui.BIM_PT_object_material, ui.BIM_PT_object_material,
ui.BIM_PT_object_psets,
ui.BIM_PT_object_qto, ui.BIM_PT_object_qto,
ui.BIM_PT_representations,
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,
@@ -322,9 +288,22 @@ if bpy is not None:
ui.BIM_ADDON_preferences, ui.BIM_ADDON_preferences,
] ]
classes.extend(module_root.classes)
classes.extend(module_aggregate.classes)
classes.extend(module_attribute.classes)
classes.extend(module_bcf.classes) classes.extend(module_bcf.classes)
classes.extend(module_context.classes)
classes.extend(module_covetool.classes) classes.extend(module_covetool.classes)
classes.extend(module_debug.classes)
classes.extend(module_geometry.classes)
classes.extend(module_model.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)")
@@ -344,7 +323,6 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_export.append(menu_func_export) bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties)
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.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion)
@@ -356,10 +334,24 @@ if bpy is not None:
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()
module_attribute.register()
module_bcf.register() module_bcf.register()
module_context.register()
module_covetool.register() module_covetool.register()
module_debug.register()
module_geometry.register()
module_model.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)
def unregister(): def unregister():
for cls in reversed(classes): for cls in reversed(classes):
@@ -368,7 +360,6 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
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.BIMDebugProperties
del bpy.types.Scene.DocProperties del bpy.types.Scene.DocProperties
del bpy.types.Scene.MapConversion del bpy.types.Scene.MapConversion
del bpy.types.Scene.TargetCRS del bpy.types.Scene.TargetCRS
@@ -379,7 +370,20 @@ if bpy is not None:
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()
module_style.unregister()
module_spatial.unregister()
module_pset.unregister()
module_project.unregister()
module_owner.unregister()
module_model.unregister() module_model.unregister()
module_geometry.unregister()
module_debug.unregister()
module_covetool.unregister() module_covetool.unregister()
module_context.unregister()
module_bcf.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)
+128 -126
View File
@@ -21,7 +21,6 @@ class BaseDecorator():
DEF_GLSL = """ DEF_GLSL = """
#define PI 3.141592653589793 #define PI 3.141592653589793
#define COLOR vec4({color[0]}, {color[1]}, {color[2]}, {color[3]})
#define MAX_POINTS 64 #define MAX_POINTS 64
#define CIRCLE_SEGS 24 #define CIRCLE_SEGS 24
""" """
@@ -99,63 +98,28 @@ class BaseDecorator():
""" """
FRAG_GLSL = """ FRAG_GLSL = """
uniform vec4 color;
out vec4 fragColor; out vec4 fragColor;
void main() { void main() {
fragColor = COLOR; fragColor = color;
} }
""" """
# class var for single handler def __init__(self):
installed = None
@classmethod
def install(cls, *args, **kwargs):
if cls.installed:
cls.uninstall()
handler = cls(*args, **kwargs)
cls.installed = SpaceView3D.draw_handler_add(handler, (), 'WINDOW', 'POST_PIXEL')
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, 'WINDOW')
except ValueError:
pass
cls.installed = None
def __init__(self, props, context):
self.context = context
self.props = props
self.shader = self.create_shader()
self.font_id = 0
self.font_size = 16
self.dpi = context.preferences.system.dpi
def create_shader(self):
defines = self.DEF_GLSL.format(color=self.props.decorations_colour)
# NB: libcode param doesn't work # NB: libcode param doesn't work
return GPUShader(vertexcode=self.VERT_GLSL, self.shader = GPUShader(vertexcode=self.VERT_GLSL,
fragcode=self.FRAG_GLSL, fragcode=self.FRAG_GLSL,
geocode=self.LIB_GLSL + self.GEOM_GLSL, geocode=self.LIB_GLSL + self.GEOM_GLSL,
defines=defines) defines=self.DEF_GLSL)
def __call__(self): def get_objects(self, collection):
if self.props.active_drawing_index is None or len(self.props.drawings) == 0:
return
for obj in self.get_objects():
self.decorate(obj)
def get_objects(self):
"""find relevant objects """find relevant objects
using class.basename using class.basename
returns: iterable of blender objects returns: iterable of blender objects
""" """
drawing = self.props.drawings[self.props.active_drawing_index] return filter(lambda o: self.basename in o.name, collection.all_objects)
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
return filter(lambda o: self.basename in o.name, collection.objects)
def get_path_geom(self, obj, topo=True): def get_path_geom(self, obj, topo=True):
"""Parses path geometry into line segments """Parses path geometry into line segments
@@ -223,13 +187,14 @@ class BaseDecorator():
return vertices, indices return vertices, indices
def decorate(self, object): def decorate(self, context, object):
"""perform actuall drawing stuff""" """perform actuall drawing stuff"""
raise NotImplementedError() raise NotImplementedError()
def draw_lines(self, obj, vertices, indices, topology=None): def draw_lines(self, context, obj, vertices, indices, topology=None):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
color = context.scene.DocProperties.decorations_colour
fmt = GPUVertFormat() fmt = GPUVertFormat()
fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
@@ -245,13 +210,20 @@ class BaseDecorator():
batch = GPUBatch(type='LINES', buf=vbo, elem=ibo) batch = GPUBatch(type='LINES', buf=vbo, elem=ibo)
bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
bgl.glEnable(bgl.GL_BLEND)
bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)
self.shader.bind() self.shader.bind()
self.shader.uniform_float self.shader.uniform_float
self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) self.shader.uniform_float("viewMatrix", region3d.perspective_matrix)
self.shader.uniform_float("winsize", (region.width, region.height)) self.shader.uniform_float("winsize", (region.width, region.height))
self.shader.uniform_float("color", color)
batch.draw(self.shader) batch.draw(self.shader)
def draw_label(self, text, pos, dir, gap=4, center=True, vcenter=False): def draw_label(self, context, text, pos, dir, gap=4, center=True, vcenter=False):
"""Draw text label """Draw text label
Args: Args:
@@ -259,15 +231,21 @@ class BaseDecorator():
aligned and centered at segment middle aligned and centered at segment middle
""" """
font_id = 0
font_size = 16
dpi = context.preferences.system.dpi
color = context.scene.DocProperties.decorations_colour
ang = -Vector((1, 0)).angle_signed(dir) ang = -Vector((1, 0)).angle_signed(dir)
cos = math.cos(ang) cos = math.cos(ang)
sin = math.sin(ang) sin = math.sin(ang)
blf.size(self.font_id, self.font_size, self.dpi) blf.size(font_id, font_size, dpi)
w, h = 0, 0 w, h = 0, 0
if center or vcenter: if center or vcenter:
w, h = blf.dimensions(self.font_id, text) w, h = blf.dimensions(font_id, text)
if center: if center:
# horizontal centering # horizontal centering
@@ -281,18 +259,18 @@ class BaseDecorator():
# side-shifting # side-shifting
pos += Vector((-sin, cos)) * gap pos += Vector((-sin, cos)) * gap
blf.enable(self.font_id, blf.ROTATION) blf.enable(font_id, blf.ROTATION)
blf.position(self.font_id, pos.x, pos.y, 0) blf.position(font_id, pos.x, pos.y, 0)
blf.rotation(self.font_id, ang) blf.rotation(font_id, ang)
blf.color(self.font_id, *self.props.decorations_colour) blf.color(font_id, *color)
blf.draw(self.font_id, text) blf.draw(font_id, text)
blf.disable(self.font_id, blf.ROTATION) blf.disable(font_id, blf.ROTATION)
def format_value(self, value): def format_value(self, context, value):
unit_system = bpy.context.scene.unit_settings.system unit_system = context.scene.unit_settings.system
if unit_system == 'IMPERIAL': if unit_system == 'IMPERIAL':
precision = bpy.context.scene.BIMProperties.imperial_precision precision = context.scene.BIMProperties.imperial_precision
if precision == "NONE": if precision == "NONE":
precision = 256 precision = 256
elif precision == "1": elif precision == "1":
@@ -313,7 +291,6 @@ class DimensionDecorator(BaseDecorator):
- puts metric text next to each segment - puts metric text next to each segment
""" """
basename = "IfcAnnotation/Dimension" basename = "IfcAnnotation/Dimension"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define ARROW_ANGLE PI / 12.0 #define ARROW_ANGLE PI / 12.0
@@ -377,14 +354,14 @@ class DimensionDecorator(BaseDecorator):
} }
""" """
def decorate(self, obj): def decorate(self, context, obj):
verts, idxs, _ = self.get_path_geom(obj, topo=False) verts, idxs, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(obj, verts, idxs) self.draw_lines(context, obj, verts, idxs)
self.draw_labels(obj, verts, idxs) self.draw_labels(context, obj, verts, idxs)
def draw_labels(self, obj, vertices, indices): def draw_labels(self, context, obj, vertices, indices):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
for i0, i1 in indices: for i0, i1 in indices:
v0 = Vector(vertices[i0]) v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1]) v1 = Vector(vertices[i1])
@@ -394,8 +371,8 @@ class DimensionDecorator(BaseDecorator):
if dir.length < 1: if dir.length < 1:
continue continue
length = (v1 - v0).length length = (v1 - v0).length
text = self.format_value(length) text = self.format_value(context, length)
self.draw_label(text, p0 + (dir) * .5, dir) self.draw_label(context, text, p0 + (dir) * .5, dir)
class EqualityDecorator(DimensionDecorator): class EqualityDecorator(DimensionDecorator):
@@ -405,11 +382,10 @@ class EqualityDecorator(DimensionDecorator):
- puts 'EQ' label - puts 'EQ' label
""" """
basename = "IfcAnnotation/Equal" basename = "IfcAnnotation/Equal"
installed = None
def draw_labels(self, obj, vertices, indices): def draw_labels(self, context, obj, vertices, indices):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
for i0, i1 in indices: for i0, i1 in indices:
v0 = Vector(vertices[i0]) v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1]) v1 = Vector(vertices[i1])
@@ -418,7 +394,7 @@ class EqualityDecorator(DimensionDecorator):
dir = p1 - p0 dir = p1 - p0
if dir.length < 1: if dir.length < 1:
continue continue
self.draw_label("EQ", p0 + (dir) * .5, dir) self.draw_label(context, "EQ", p0 + (dir) * .5, dir)
class LeaderDecorator(BaseDecorator): class LeaderDecorator(BaseDecorator):
@@ -427,7 +403,6 @@ class LeaderDecorator(BaseDecorator):
- middle points w/out decorations - middle points w/out decorations
""" """
basename = "IfcAnnotation/Leader" basename = "IfcAnnotation/Leader"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define ARROW_ANGLE PI / 12.0 #define ARROW_ANGLE PI / 12.0
@@ -484,9 +459,9 @@ class LeaderDecorator(BaseDecorator):
} }
""" """
def decorate(self, obj): def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj) verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(obj, verts, idxs, topo) self.draw_lines(context, obj, verts, idxs, topo)
class StairDecorator(BaseDecorator): class StairDecorator(BaseDecorator):
@@ -496,7 +471,6 @@ class StairDecorator(BaseDecorator):
- middle points w/out decorations - middle points w/out decorations
""" """
basename = "IfcAnnotation/Stair" basename = "IfcAnnotation/Stair"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define CIRCLE_SIZE 8.0 #define CIRCLE_SIZE 8.0
@@ -570,14 +544,13 @@ class StairDecorator(BaseDecorator):
} }
""" """
def decorate(self, obj): def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj) verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(obj, verts, idxs, topo) self.draw_lines(context, obj, verts, idxs, topo)
class HiddenDecorator(BaseDecorator): class HiddenDecorator(BaseDecorator):
basename = "IfcAnnotation/Hidden" basename = "IfcAnnotation/Hidden"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define DASH_SIZE 16.0 #define DASH_SIZE 16.0
@@ -619,6 +592,7 @@ class HiddenDecorator(BaseDecorator):
""" """
FRAG_GLSL = """ FRAG_GLSL = """
uniform vec4 color;
in vec2 gl_FragCoord; in vec2 gl_FragCoord;
in float dist; in float dist;
out vec4 fragColor; out vec4 fragColor;
@@ -626,21 +600,20 @@ class HiddenDecorator(BaseDecorator):
void main() { void main() {
uint bit = uint(fract(dist / DASH_SIZE) * 32); uint bit = uint(fract(dist / DASH_SIZE) * 32);
if ((DASH_PATTERN & (1U<<bit)) == 0U) discard; if ((DASH_PATTERN & (1U<<bit)) == 0U) discard;
fragColor = COLOR; fragColor = color;
} }
""" """
def decorate(self, obj): def decorate(self, context, obj):
if obj.data.is_editmode: if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj) verts, idxs = self.get_editmesh_geom(obj)
else: else:
verts, idxs = self.get_mesh_geom(obj) verts, idxs = self.get_mesh_geom(obj)
self.draw_lines(obj, verts, idxs) self.draw_lines(context, obj, verts, idxs)
class MiscDecorator(HiddenDecorator): class MiscDecorator(HiddenDecorator):
basename = "IfcAnnotation/Misc" basename = "IfcAnnotation/Misc"
installed = None
FRAG_GLSL = BaseDecorator.FRAG_GLSL FRAG_GLSL = BaseDecorator.FRAG_GLSL
@@ -661,16 +634,15 @@ class LevelDecorator(BaseDecorator):
continue continue
yield [obj.matrix_world @ p.co for p in spline_points] yield [obj.matrix_world @ p.co for p in spline_points]
def decorate(self, obj): def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj) verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(obj, verts, idxs, topo) self.draw_lines(context, obj, verts, idxs, topo)
splines = self.get_splines(obj) splines = self.get_splines(obj)
self.draw_labels(obj, splines) self.draw_labels(context, obj, splines)
class PlanDecorator(LevelDecorator): class PlanDecorator(LevelDecorator):
basename = "IfcAnnotation/Plan Level" basename = "IfcAnnotation/Plan Level"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define CIRCLE_SIZE 8.0 #define CIRCLE_SIZE 8.0
@@ -736,9 +708,9 @@ class PlanDecorator(LevelDecorator):
} }
""" """
def draw_labels(self, obj, splines): def draw_labels(self, context, obj, splines):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
for verts in splines: for verts in splines:
v0 = verts[0] v0 = verts[0]
v1 = verts[1] v1 = verts[1]
@@ -747,13 +719,12 @@ class PlanDecorator(LevelDecorator):
dir = p1 - p0 dir = p1 - p0
if dir.length < 1: if dir.length < 1:
continue continue
text = "RL " + self.format_value(verts[-1].z) text = "RL " + self.format_value(context, verts[-1].z)
self.draw_label(text, p0, dir, gap=8, center=False) self.draw_label(context, text, p0, dir, gap=8, center=False)
class SectionDecorator(LevelDecorator): class SectionDecorator(LevelDecorator):
basename = "IfcAnnotation/Section Level" basename = "IfcAnnotation/Section Level"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define CALLOUT_GAP 8.0 #define CALLOUT_GAP 8.0
@@ -816,9 +787,9 @@ class SectionDecorator(LevelDecorator):
} }
""" """
def draw_labels(self, obj, splines): def draw_labels(self, context, obj, splines):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
for verts in splines: for verts in splines:
v0 = verts[0] v0 = verts[0]
v1 = verts[1] v1 = verts[1]
@@ -827,8 +798,8 @@ class SectionDecorator(LevelDecorator):
dir = p1 - p0 dir = p1 - p0
if dir.length < 1: if dir.length < 1:
continue continue
text = "RL " + self.format_value(verts[-1].z) text = "RL " + self.format_value(context, verts[-1].z)
self.draw_label(text, p0 + dir.normalized() * 16, -dir, gap=16, center=False) self.draw_label(context, text, p0 + dir.normalized() * 16, -dir, gap=16, center=False)
class BreakDecorator(BaseDecorator): class BreakDecorator(BaseDecorator):
@@ -838,7 +809,6 @@ class BreakDecorator(BaseDecorator):
Uses first two vertices in verts list. Uses first two vertices in verts list.
""" """
basename = "IfcAnnotation/Break" basename = "IfcAnnotation/Break"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define BREAK_LENGTH 32.0 #define BREAK_LENGTH 32.0
@@ -893,12 +863,12 @@ class BreakDecorator(BaseDecorator):
} }
""" """
def decorate(self, obj): def decorate(self, context, obj):
if obj.data.is_editmode: if obj.data.is_editmode:
verts = self.get_editmesh_geom(obj) verts = self.get_editmesh_geom(obj)
else: else:
verts = self.get_mesh_geom(obj) verts = self.get_mesh_geom(obj)
self.draw_lines(obj, verts, [(0, 1)]) self.draw_lines(context, obj, verts, [(0, 1)])
def get_mesh_geom(self, obj): def get_mesh_geom(self, obj):
# first vertices only # first vertices only
@@ -914,7 +884,6 @@ class BreakDecorator(BaseDecorator):
class GridDecorator(BaseDecorator): class GridDecorator(BaseDecorator):
basename = "IfcGridAxis/" basename = "IfcGridAxis/"
installed = None
DEF_GLSL = BaseDecorator.DEF_GLSL + """ DEF_GLSL = BaseDecorator.DEF_GLSL + """
#define CIRCLE_SIZE 16.0 #define CIRCLE_SIZE 16.0
@@ -983,6 +952,7 @@ class GridDecorator(BaseDecorator):
""" """
FRAG_GLSL = """ FRAG_GLSL = """
uniform vec4 color;
in vec2 gl_FragCoord; in vec2 gl_FragCoord;
in float dist; in float dist;
out vec4 fragColor; out vec4 fragColor;
@@ -990,7 +960,7 @@ class GridDecorator(BaseDecorator):
void main() { void main() {
uint bit = uint(fract(dist / DASH_SIZE) * 32); uint bit = uint(fract(dist / DASH_SIZE) * 32);
if ((DASH_PATTERN & (1U<<bit)) == 0U) discard; if ((DASH_PATTERN & (1U<<bit)) == 0U) discard;
fragColor = COLOR; fragColor = color;
} }
""" """
@@ -1005,36 +975,68 @@ class GridDecorator(BaseDecorator):
vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts] vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts]
return vertices return vertices
def decorate(self, obj): def decorate(self, context, obj):
if obj.data.is_editmode: if obj.data.is_editmode:
verts = self.get_editmesh_geom(obj) verts = self.get_editmesh_geom(obj)
else: else:
verts = self.get_mesh_geom(obj) verts = self.get_mesh_geom(obj)
self.draw_lines(obj, verts, [(0, 1)]) self.draw_lines(context, obj, verts, [(0, 1)])
self.draw_labels(obj, verts) self.draw_labels(context, obj, verts)
def draw_labels(self, obj, vertices): def draw_labels(self, context, obj, vertices):
region = self.context.region region = context.region
region3d = self.context.region_data region3d = context.region_data
v0 = Vector(vertices[0]) v0 = Vector(vertices[0])
v1 = Vector(vertices[1]) v1 = Vector(vertices[1])
p0 = location_3d_to_region_2d(region, region3d, v0) p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1) p1 = location_3d_to_region_2d(region, region3d, v1)
dir = Vector((1, 0)) dir = Vector((1, 0))
text = obj.BIMObjectProperties.attributes['AxisTag'].string_value text = obj.BIMObjectProperties.attributes['AxisTag'].string_value
self.draw_label(text, p0, dir, vcenter=True, gap=0) self.draw_label(context, text, p0, dir, vcenter=True, gap=0)
self.draw_label(text, p1, dir, vcenter=True, gap=0) self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
all_decorators = [ class DecorationsHandler():
DimensionDecorator, decorators_classes = [
EqualityDecorator, DimensionDecorator,
GridDecorator, EqualityDecorator,
HiddenDecorator, GridDecorator,
LeaderDecorator, HiddenDecorator,
MiscDecorator, LeaderDecorator,
PlanDecorator, MiscDecorator,
SectionDecorator, PlanDecorator,
StairDecorator, SectionDecorator,
BreakDecorator StairDecorator,
] BreakDecorator
]
installed = None
@classmethod
def install(cls, context):
if cls.installed:
cls.uninstall()
handler = cls()
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), 'WINDOW', 'POST_PIXEL')
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, 'WINDOW')
except ValueError:
pass
cls.installed = None
def __init__(self):
self.decorators = [cls() for cls in self.decorators_classes]
def __call__(self, context):
props = context.scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return
drawing = props.drawings[props.active_drawing_index]
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
for decorator in self.decorators:
for obj in decorator.get_objects(collection):
decorator.decorate(context, obj)
+70 -100
View File
@@ -8,7 +8,6 @@ import os
import zipfile import zipfile
import tempfile import tempfile
import ifcopenshell import ifcopenshell
import ifcopenshell.util.pset
import ifcopenshell.util.schema import ifcopenshell.util.schema
from pathlib import Path from pathlib import Path
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
@@ -60,7 +59,6 @@ class IfcParser:
self.rel_contained_in_spatial_structure = {} self.rel_contained_in_spatial_structure = {}
self.rel_nests = {} self.rel_nests = {}
self.rel_space_boundaries = {} self.rel_space_boundaries = {}
self.rel_defines_by_type = {}
self.rel_defines_by_qto = {} self.rel_defines_by_qto = {}
self.rel_defines_by_pset = {} self.rel_defines_by_pset = {}
self.rel_associates_document_object = {} self.rel_associates_document_object = {}
@@ -372,9 +370,6 @@ class IfcParser:
product.update(metadata_override) product.update(metadata_override)
type_product = obj.BIMObjectProperties.relating_type type_product = obj.BIMObjectProperties.relating_type
if type_product and self.is_a_type(self.get_ifc_class(type_product.name)):
reference = self.get_type_product_reference(type_product.name)
self.rel_defines_by_type.setdefault(reference, []).append(self.product_index)
if product["has_boundary_condition"]: if product["has_boundary_condition"]:
product["boundary_condition_class"] = obj.BIMObjectProperties.boundary_condition.name product["boundary_condition_class"] = obj.BIMObjectProperties.boundary_condition.name
@@ -472,39 +467,28 @@ class IfcParser:
continue continue
results[item_key] = {"ifc": None, "raw": raw, "material": material, "attributes": {"Name": item.name}} results[item_key] = {"ifc": None, "raw": raw, "material": material, "attributes": {"Name": item.name}}
def add_automatic_qtos(self, ifc_class, obj): def add_automatic_qtos(self, ifc_class: str, obj):
if not obj.data: if not obj.data:
return return
qto_names = self.get_applicable_qtos(ifc_class) applicable_qtos = schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
for name in qto_names: for applicable_qto in applicable_qtos:
if name not in ifcopenshell.util.pset.qtos:
continue
has_automatic_value = False has_automatic_value = False
props = ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys()
guessed_values = {} guessed_values = {}
for prop_name in props: prop_names = [p.Name for p in applicable_qto.HasPropertyTemplates]
value = self.qto_calculator.guess_quantity(prop_name, props, obj) for prop_name in prop_names:
value = self.qto_calculator.guess_quantity(prop_name, prop_names, obj)
if value: if value:
guessed_values[prop_name] = value guessed_values[prop_name] = value
has_automatic_value = True has_automatic_value = True
if has_automatic_value: if has_automatic_value:
qto = obj.BIMObjectProperties.qtos.add() qto = obj.BIMObjectProperties.qtos.add()
qto.name = name qto.name = applicable_qto.Name
for prop_name in props: for prop_name in prop_names:
prop = qto.properties.add() prop = qto.properties.add()
prop.name = prop_name prop.name = prop_name
if prop_name in guessed_values: if prop_name in guessed_values:
prop.string_value = str(guessed_values[prop_name]) prop.string_value = str(guessed_values[prop_name])
def get_applicable_qtos(self, ifc_class):
results = []
empty = ifcopenshell.file(schema=self.ifc_export_settings.schema)
element = empty.create_entity(ifc_class)
for ifc_class, qto_names in schema.ifc.applicable_qtos.items():
if element.is_a(ifc_class):
results.extend(qto_names)
return results
def get_product_relating_structure(self, product, obj): def get_product_relating_structure(self, product, obj):
relating_structure = obj.BIMObjectProperties.relating_structure relating_structure = obj.BIMObjectProperties.relating_structure
if relating_structure: if relating_structure:
@@ -1057,29 +1041,32 @@ class IfcParser:
for context in self.ifc_export_settings.context_tree: for context in self.ifc_export_settings.context_tree:
for subcontext in context["subcontexts"]: for subcontext in context["subcontexts"]:
for target_view in subcontext["target_views"]: for target_view in subcontext["target_views"]:
rep_context = self.get_obj_representation_context(obj, context["name"], subcontext["name"], target_view) representation = self.get_shape_representation(obj, context["name"], subcontext["name"], target_view)
if rep_context: if representation:
self.append_representation_in_context(obj, rep_context, name) self.append_representation_in_context(obj, representation, name)
def get_obj_representation_context(self, obj, context, subcontext, target_view): def get_shape_representation(self, obj, context, subcontext, target_view):
for c in obj.BIMObjectProperties.representation_contexts: for representation in obj.BIMObjectProperties.representations:
if c.context == context and c.name == subcontext and c.target_view == target_view: c = self.stored_file.by_id(representation.ifc_definition_id)
return c if c.ContextType == context and c.ContextIdentifier == subcontext and c.TargetView == target_view:
if obj.BIMObjectProperties.representation_contexts: return representation
if obj.BIMObjectProperties.representations:
return return
if context == "Model" and subcontext == "Body" and target_view == "MODEL_VIEW": # TODO: reimplement - see bug #1222
representation_context = obj.BIMObjectProperties.representation_contexts.add() #if context == "Model" and subcontext == "Body" and target_view == "MODEL_VIEW":
representation_context.context = "Model" # representation_context = obj.BIMObjectProperties.representation_contexts.add()
representation_context.name = "Body" # representation_context.context = "Model"
representation_context.target_view = "MODEL_VIEW" # representation_context.name = "Body"
return representation_context # representation_context.target_view = "MODEL_VIEW"
# return representation_context
def append_representation_in_context(self, obj, rep_context, name): def append_representation_in_context(self, obj, shape_representation, name):
context = rep_context.context context_of_items = self.stored_file.by_id(shape_representation.ifc_definition_id).ContextOfItems
subcontext = rep_context.name context = context_of_items.ContextType
target_view = rep_context.target_view subcontext = context_of_items.ContextIdentifier
target_view = context_of_items.TargetView
if self.ifc_export_settings.should_roundtrip_native and rep_context.ifc_definition_id: if self.ifc_export_settings.should_roundtrip_native and shape_representation.ifc_definition_id:
self.representations[ self.representations[
"{}/{}/{}/{}".format(context, subcontext, target_view, name) "{}/{}/{}/{}".format(context, subcontext, target_view, name)
] = self.get_representation(obj.data, obj, context, subcontext, target_view) ] = self.get_representation(obj.data, obj, context, subcontext, target_view)
@@ -1088,7 +1075,6 @@ class IfcParser:
context_prefix = "/".join([context, subcontext, target_view]) context_prefix = "/".join([context, subcontext, target_view])
mesh_name = "/".join([context_prefix, name]) mesh_name = "/".join([context_prefix, name])
mesh = self.search_for_mesh_or_curve_data(mesh_name) mesh = self.search_for_mesh_or_curve_data(mesh_name)
# TODO: if the search result is empty, we should check for ifc_definition_id
if mesh: if mesh:
self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view) self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view)
if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW": if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW":
@@ -1113,7 +1099,7 @@ class IfcParser:
return data return data
def get_representation(self, mesh, obj, context, subcontext, target_view): def get_representation(self, mesh, obj, context, subcontext, target_view):
rep_context = self.get_obj_representation_context(obj, context, subcontext, target_view) representation = self.get_shape_representation(obj, context, subcontext, target_view)
return { return {
"ifc": None, "ifc": None,
"raw": mesh, "raw": mesh,
@@ -1121,9 +1107,9 @@ class IfcParser:
"context": context, "context": context,
"subcontext": subcontext, "subcontext": subcontext,
"target_view": target_view, "target_view": target_view,
"has_ifc_definition": rep_context and rep_context.ifc_definition_id, "has_ifc_definition": representation and representation.ifc_definition_id,
"ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None, "ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None,
"ifc_definition_id": rep_context.ifc_definition_id if rep_context else 0 "ifc_definition_id": representation.ifc_definition_id if representation else 0
if hasattr(mesh, "BIMMeshProperties") if hasattr(mesh, "BIMMeshProperties")
else None, else None,
"is_parametric": mesh.BIMMeshProperties.is_parametric if hasattr(mesh, "BIMMeshProperties") else False, "is_parametric": mesh.BIMMeshProperties.is_parametric if hasattr(mesh, "BIMMeshProperties") else False,
@@ -1345,8 +1331,9 @@ class IfcExporter:
self.roundtrip_id_new_to_old = {} self.roundtrip_id_new_to_old = {}
def export(self, selected_objects): def export(self, selected_objects):
self.file = ifc.IfcStore.get_file() self.stored_file = ifc.IfcStore.get_file() # See bug #1222
if self.file and self.ifc_export_settings.should_export_from_memory: if self.stored_file and self.ifc_export_settings.should_export_from_memory:
self.file = self.stored_file
return self.write_ifc_file() return self.write_ifc_file()
self.schema_version = self.ifc_export_settings.schema self.schema_version = self.ifc_export_settings.schema
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version) self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version)
@@ -1383,7 +1370,6 @@ class IfcExporter:
self.relate_objects_to_objects() self.relate_objects_to_objects()
self.relate_elements_to_spatial_structures() self.relate_elements_to_spatial_structures()
self.relate_nested_elements_to_hosted_elements() self.relate_nested_elements_to_hosted_elements()
self.relate_objects_to_types()
self.relate_objects_to_qtos() self.relate_objects_to_qtos()
self.relate_objects_to_psets() self.relate_objects_to_psets()
self.relate_objects_to_opening_elements() self.relate_objects_to_opening_elements()
@@ -1423,15 +1409,16 @@ class IfcExporter:
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format( self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
self.get_application_name(), self.get_application_version() self.get_application_name(), self.get_application_version()
) )
if self.owner_history: # TODO: reimplement. See #1222.
if self.schema_version == "IFC2X3": #if self.owner_history:
self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id # if self.schema_version == "IFC2X3":
else: # self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id
self.file.wrapped_data.header.file_name.authorization = ( # else:
self.owner_history.OwningUser.ThePerson.Identification # self.file.wrapped_data.header.file_name.authorization = (
) # self.owner_history.OwningUser.ThePerson.Identification
else: # )
self.file.wrapped_data.header.file_name.authorization = "Nobody" #else:
# self.file.wrapped_data.header.file_name.authorization = "Nobody"
def get_application_name(self): def get_application_name(self):
return "BlenderBIM" return "BlenderBIM"
@@ -1699,13 +1686,15 @@ class IfcExporter:
pset["ifc"] = self.file.create_entity("IfcMaterialProperties", **pset["attributes"]) pset["ifc"] = self.file.create_entity("IfcMaterialProperties", **pset["attributes"])
def create_qto_properties(self, qto): def create_qto_properties(self, qto):
if qto["attributes"]["Name"] in ifcopenshell.util.pset.qtos: qto_template = schema.ifc.psetqto.get_by_name(qto["attributes"]["Name"])
return self.create_templated_qto_properties(qto) if qto_template:
return self.create_templated_qto_properties(qto, qto_template)
return self.create_custom_qto_properties(qto) return self.create_custom_qto_properties(qto)
def create_pset_properties(self, pset): def create_pset_properties(self, pset):
if pset["attributes"]["Name"] in ifcopenshell.util.pset.psets: pset_template = schema.ifc.psetqto.get_by_name(pset["attributes"]["Name"])
return self.create_templated_pset_properties(pset) if pset_template:
return self.create_templated_pset_properties(pset, pset_template)
return self.create_custom_pset_properties(pset) return self.create_custom_pset_properties(pset)
def create_custom_pset_properties(self, pset): def create_custom_pset_properties(self, pset):
@@ -1735,15 +1724,15 @@ class IfcExporter:
) )
return properties return properties
def create_templated_pset_properties(self, pset): def create_templated_pset_properties(self, pset, pset_template):
properties = [] properties = []
templates = ifcopenshell.util.pset.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"] for prop in pset_template.HasPropertyTemplates:
for name, data in templates.items(): name = prop.Name
if name not in pset["raw"]: if name not in pset["raw"]:
continue continue
if data.TemplateType == "P_SINGLEVALUE" or data.TemplateType == "P_ENUMERATEDVALUE": if prop.TemplateType == "P_SINGLEVALUE" or prop.TemplateType == "P_ENUMERATEDVALUE":
if data.PrimaryMeasureType: if prop.PrimaryMeasureType:
value_type = data.PrimaryMeasureType value_type = prop.PrimaryMeasureType
else: else:
# The IFC spec is missing some, so we provide a fallback # The IFC spec is missing some, so we provide a fallback
value_type = "IfcLabel" value_type = "IfcLabel"
@@ -1753,7 +1742,8 @@ class IfcExporter:
properties.append( properties.append(
self.file.create_entity("IfcPropertySingleValue", **{"Name": name, "NominalValue": nominal_value}) self.file.create_entity("IfcPropertySingleValue", **{"Name": name, "NominalValue": nominal_value})
) )
invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates.keys()] templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates]
invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates_names]
if invalid_pset_keys: if invalid_pset_keys:
self.ifc_export_settings.logger.error( self.ifc_export_settings.logger.error(
"One or more properties were invalid in the pset {}: {}".format( "One or more properties were invalid in the pset {}: {}".format(
@@ -1762,20 +1752,21 @@ class IfcExporter:
) )
return properties return properties
def create_templated_qto_properties(self, qto): def create_templated_qto_properties(self, qto, qto_template):
properties = [] properties = []
templates = ifcopenshell.util.pset.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"] for prop in qto_template.HasPropertyTemplates:
for name, data in templates.items(): name = prop.Name
if name not in qto["raw"]: if name not in qto["raw"]:
continue continue
if data.TemplateType[0:2] == "Q_": if prop.TemplateType[0:2] == "Q_":
value_basename = data.TemplateType[2:].title() value_basename = prop.TemplateType[2:].title()
value_name = f"{value_basename}Value" value_name = f"{value_basename}Value"
class_name = f"IfcQuantity{value_basename}" class_name = f"IfcQuantity{value_basename}"
properties.append( properties.append(
self.file.create_entity(class_name, **{"Name": name, value_name: float(qto["raw"][name])}) self.file.create_entity(class_name, **{"Name": name, value_name: float(qto["raw"][name])})
) )
invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates.keys()] templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates]
invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates_names]
if invalid_qto_keys: if invalid_qto_keys:
self.ifc_export_settings.logger.error( self.ifc_export_settings.logger.error(
"One or more properties were invalid in the qto {}/{}: {}".format( "One or more properties were invalid in the qto {}/{}: {}".format(
@@ -2039,8 +2030,8 @@ class IfcExporter:
# At the moment, we assume that styled items only apply to the body context. # At the moment, we assume that styled items only apply to the body context.
if representation.RepresentationIdentifier != "Body": if representation.RepresentationIdentifier != "Body":
continue continue
rep_context = self.ifc_parser.get_obj_representation_context(product["raw"], "Model", "Body", "MODEL_VIEW") rep = self.ifc_parser.get_shape_representation(product["raw"], "Model", "Body", "MODEL_VIEW")
if self.ifc_export_settings.should_roundtrip_native and rep_context and rep_context.ifc_definition_id: if self.ifc_export_settings.should_roundtrip_native and rep and rep.ifc_definition_id:
# For native roundtripping, each slot could be a one to many relationship to items # For native roundtripping, each slot could be a one to many relationship to items
for item in self.get_geometric_representation_items(representation): for item in self.get_geometric_representation_items(representation):
original_id = self.roundtrip_id_new_to_old[item.id()] original_id = self.roundtrip_id_new_to_old[item.id()]
@@ -3181,17 +3172,6 @@ class IfcExporter:
[o["ifc"] for o in related_objects], [o["ifc"] for o in related_objects],
) )
def relate_objects_to_types(self):
for relating_type, related_objects in self.ifc_parser.rel_defines_by_type.items():
self.file.createIfcRelDefinesByType(
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[self.ifc_parser.products[o]["ifc"] for o in related_objects],
self.ifc_parser.type_products[relating_type]["ifc"],
)
def relate_objects_to_qtos(self): def relate_objects_to_qtos(self):
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items(): for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items():
self.file.createIfcRelDefinesByProperties( self.file.createIfcRelDefinesByProperties(
@@ -3468,6 +3448,7 @@ class IfcExporter:
return co * self.ifc_parser.unit_scale return co * self.ifc_parser.unit_scale
def write_ifc_file(self): def write_ifc_file(self):
self.set_header()
extension = self.ifc_export_settings.output_file.split(".")[-1] extension = self.ifc_export_settings.output_file.split(".")[-1]
if extension == "ifczip": if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path: with tempfile.TemporaryDirectory() as unzipped_path:
@@ -3551,15 +3532,4 @@ class IfcExportSettings:
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.should_export_from_memory = scene_bim.export_should_export_from_memory settings.should_export_from_memory = scene_bim.export_should_export_from_memory
settings.context_tree = [] settings.context_tree = []
for ifc_context in ["model", "plan"]:
if getattr(scene_bim, "has_{}_context".format(ifc_context)):
subcontexts = {}
for subcontext in getattr(scene_bim, "{}_subcontexts".format(ifc_context)):
subcontexts.setdefault(subcontext.name, []).append(subcontext.target_view)
settings.context_tree.append(
{
"name": ifc_context.title(),
"subcontexts": [{"name": key, "target_views": value} for key, value in subcontexts.items()],
}
)
return settings return settings
@@ -1,4 +1,6 @@
import math import math
from mathutils import geometry
from mathutils import Vector
import bpy import bpy
@@ -285,3 +287,84 @@ def format_distance(value, isArea=False, hide_units=True):
tx_dist = fmt % value tx_dist = fmt % value
return tx_dist return tx_dist
def parse_diagram_scale(camera):
"""Returns numeric value of scale"""
if camera.BIMCameraProperties.diagram_scale == "CUSTOM":
_, fraction = camera.BIMCameraProperties.custom_diagram_scale.split("|")
else:
_, fraction = camera.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
return float(numerator) / float(denominator)
def ortho_view_frame(camera, margin=0.015):
"""Calculates 2d bounding box of camera view area.
Similar to `bpy.types.Camera.view_frame`
:arg camera: camera of drawing
:type camera: bpy.types.Camera + BIMCameraProperties
:arg margin: margins, in scene units
:type margin: float
:return: (xmin, xmax, ymin, ymax) in local camera coordinates
"""
aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x
size = camera.ortho_scale
hwidth = size * .5
hheight = size * .5 * aspect
scale = parse_diagram_scale(camera)
xmarg = margin * scale
ymarg = margin * scale * aspect
return (-hwidth + xmarg, hwidth - xmarg, -hheight + ymarg, hheight - ymarg)
def clip_segment(bounds, segm):
"""Clipping line segment to bounds
: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
"""
# LiangBarsky algorithm
xmin, xmax, ymin, ymax = bounds
p1, p2 = segm
def clip_side(p, q):
if abs(p) < 1e-10: # ~= 0, parallel to the side
if q < 0:
return None # outside
else:
return 0, 1 # inside
t = q / p # the intersection point
if p < 0: # entering
return t, 1
else: # leaving
return 0, t
dlt = p2 - p1
tt = (
clip_side(-dlt.x, p1.x - xmin), # left
clip_side(+dlt.x, xmax - p1.x), # right
clip_side(-dlt.y, p1.y - ymin), # bottom
clip_side(+dlt.y, ymax - p1.y), # top
)
if None in tt:
return None
t1 = max(0, max(t[0] for t in tt))
t2 = min(1, min(t[1] for t in tt))
if t1 >= t2:
return None
p1c = p1 + dlt * t1
p2c = p1 + dlt * t2
return p1c, p2c
@@ -5,6 +5,7 @@ import ifcopenshell
class IfcStore: class IfcStore:
path = "" path = ""
file = None file = None
schema = None
pset_template_path = "" pset_template_path = ""
pset_template_file = None pset_template_file = None
@@ -15,3 +16,11 @@ class IfcStore:
if IfcStore.path: if IfcStore.path:
IfcStore.file = ifcopenshell.open(IfcStore.path) IfcStore.file = ifcopenshell.open(IfcStore.path)
return IfcStore.file return IfcStore.file
@staticmethod
def get_schema():
if IfcStore.file is None:
return
elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
return IfcStore.schema
+35 -109
View File
@@ -3,7 +3,7 @@ import ifcopenshell.geom
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import ifcopenshell.util.selector import ifcopenshell.util.selector
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.pset import ifcopenshell.util.unit
import bpy import bpy
import bmesh import bmesh
import os import os
@@ -20,8 +20,8 @@ import tempfile
from pathlib import Path from pathlib import Path
from itertools import cycle from itertools import cycle
from datetime import datetime from datetime import datetime
from . import helper
from . import ifc from . import ifc
from . import schema
class FileCopy(threading.Thread): class FileCopy(threading.Thread):
@@ -256,6 +256,7 @@ class MaterialCreator:
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())
self.ifc_importer.add_element_attributes(material, obj.BIMMaterialProperties) self.ifc_importer.add_element_attributes(material, obj.BIMMaterialProperties)
for pset in getattr(material, "HasProperties", ()): for pset in getattr(material, "HasProperties", ()):
self.ifc_importer.add_pset(pset, obj.BIMMaterialProperties) self.ifc_importer.add_pset(pset, obj.BIMMaterialProperties)
@@ -286,6 +287,7 @@ class MaterialCreator:
for style in styles: for style in styles:
if not style.is_a("IfcSurfaceStyle"): if not style.is_a("IfcSurfaceStyle"):
continue continue
material.BIMMaterialProperties.ifc_style_id = int(style.id())
external_style = None 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"):
@@ -373,7 +375,6 @@ class IfcImporter:
self.native_data = {} self.native_data = {}
self.groups = {} self.groups = {}
self.aggregates = {} self.aggregates = {}
self.aggregate_collection = None
self.aggregate_collections = {} self.aggregate_collections = {}
self.material_creator = MaterialCreator(ifc_import_settings, self) self.material_creator = MaterialCreator(ifc_import_settings, self)
@@ -409,8 +410,6 @@ class IfcImporter:
self.profile_code("Patching ifc") self.profile_code("Patching ifc")
self.set_units() self.set_units()
self.profile_code("Set units") self.profile_code("Set units")
self.create_geometric_representation_contexts()
self.profile_code("Create contexts")
self.create_project() self.create_project()
self.profile_code("Create project") self.profile_code("Create project")
self.create_classifications() self.create_classifications()
@@ -773,6 +772,7 @@ class IfcImporter:
obj = self.existing_elements[element.GlobalId] obj = self.existing_elements[element.GlobalId]
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()
self.add_element_attributes(element, obj.BIMObjectProperties) 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}
@@ -809,6 +809,7 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve) shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape) mesh = self.create_mesh(axis, shape)
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.matrix_world = matrix_world obj.matrix_world = matrix_world
self.add_element_attributes(axis, obj.BIMObjectProperties) self.add_element_attributes(axis, obj.BIMObjectProperties)
grid.objects.link(obj) grid.objects.link(obj)
@@ -843,12 +844,10 @@ class IfcImporter:
except: except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element) self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
obj = bpy.data.objects.new(self.get_name(element), mesh) obj = bpy.data.objects.new(self.get_name(element), mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
self.material_creator.create(element, obj, mesh) self.material_creator.create(element, obj, mesh)
self.add_element_attributes(element, obj.BIMObjectProperties)
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_type_product_psets(element, obj)
self.add_product_representations(element, obj)
self.type_collection.objects.link(obj) self.type_collection.objects.link(obj)
self.type_products[element.GlobalId] = obj self.type_products[element.GlobalId] = obj
@@ -950,11 +949,16 @@ class IfcImporter:
mesh = self.create_native_mesh(element, shape) mesh = self.create_native_mesh(element, shape)
if mesh is None: if mesh is None:
mesh = self.create_mesh(element, shape) mesh = self.create_mesh(element, shape)
if "-" in shape.geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id.split("-")[0])
else:
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id)
self.meshes[mesh_name] = mesh self.meshes[mesh_name] = mesh
else: else:
mesh = None mesh = None
obj = bpy.data.objects.new(self.get_name(element), mesh) obj = bpy.data.objects.new(self.get_name(element), mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
if shape: if shape:
m = shape.transformation.matrix.data m = shape.transformation.matrix.data
@@ -968,13 +972,10 @@ class IfcImporter:
obj.matrix_world = self.get_element_matrix(element) obj.matrix_world = self.get_element_matrix(element)
self.add_element_representation_items(element, obj) self.add_element_representation_items(element, obj)
self.add_element_attributes(element, obj.BIMObjectProperties)
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_defines_by_type_relation(element, obj)
self.add_opening_relation(element, obj) self.add_opening_relation(element, obj)
self.add_product_definitions(element, obj) self.add_product_definitions(element, obj)
self.add_product_representations(element, obj)
self.added_data[element.GlobalId] = obj self.added_data[element.GlobalId] = obj
if element.is_a("IfcOpeningElement"): if element.is_a("IfcOpeningElement"):
@@ -1309,7 +1310,6 @@ class IfcImporter:
# Occurs when reloading a project # Occurs when reloading a project
pass pass
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name] project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
project_collection.children[self.aggregate_collection.name].hide_viewport = True
project_collection.children[self.opening_collection.name].hide_viewport = True project_collection.children[self.opening_collection.name].hide_viewport = True
project_collection.children[self.type_collection.name].hide_viewport = True project_collection.children[self.type_collection.name].hide_viewport = True
@@ -1355,47 +1355,21 @@ 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_representations(self, element, obj):
if element.is_a("IfcProduct"):
if not element.Representation:
return
for r in element.Representation.Representations:
new = obj.BIMObjectProperties.representations.add()
new.name = r.RepresentationIdentifier
new.type = r.RepresentationType
new.ifc_definition_id = r.id()
elif element.is_a("IfcTypeProduct"):
if not element.RepresentationMaps:
return
for r in element.RepresentationMaps:
new = obj.BIMObjectProperties.representations.add()
new.name = r.MappedRepresentation.RepresentationIdentifier
new.type = r.MappedRepresentation.RepresentationType
new.ifc_definition_id = r.MappedRepresentation.id()
def add_product_definitions(self, element, obj): def add_product_definitions(self, element, obj):
if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy: if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy:
return return
for definition in element.IsDefinedBy: for definition in element.IsDefinedBy:
if not definition.is_a("IfcRelDefinesByProperties"): if not definition.is_a("IfcRelDefinesByProperties"):
continue continue
if definition.RelatingPropertyDefinition.is_a("IfcPropertySet"): if definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
self.add_pset(definition.RelatingPropertyDefinition, obj.BIMObjectProperties)
elif definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
self.add_qto(definition.RelatingPropertyDefinition, obj) self.add_qto(definition.RelatingPropertyDefinition, obj)
def add_type_product_psets(self, element, obj):
if not hasattr(element, "HasPropertySets") or not element.HasPropertySets:
return
for definition in element.HasPropertySets:
if definition.is_a("IfcPropertySet"):
self.add_pset(definition, obj.BIMObjectProperties)
def add_pset(self, pset, props): def add_pset(self, pset, props):
new_pset = props.psets.add() new_pset = props.psets.add()
new_pset.name = pset.Name new_pset.name = pset.Name
if new_pset.name in ifcopenshell.util.pset.psets: pset_template = schema.ifc.psetqto.get_by_name(new_pset.name)
for prop_name in ifcopenshell.util.pset.psets[new_pset.name]["HasPropertyTemplates"].keys(): if pset_template:
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = new_pset.properties.add() prop = new_pset.properties.add()
prop.name = prop_name prop.name = prop_name
try: try:
@@ -1421,8 +1395,9 @@ class IfcImporter:
def add_qto(self, qto, obj): def add_qto(self, qto, obj):
new_qto = obj.BIMObjectProperties.qtos.add() new_qto = obj.BIMObjectProperties.qtos.add()
new_qto.name = str(qto.Name) new_qto.name = str(qto.Name)
if new_qto.name in ifcopenshell.util.pset.qtos: qto_template = schema.ifc.psetqto.get_by_name(new_qto.name)
for prop_name in ifcopenshell.util.pset.qtos[new_qto.name]["HasPropertyTemplates"].keys(): if qto_template:
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = new_qto.properties.add() prop = new_qto.properties.add()
prop.name = prop_name prop.name = prop_name
for prop in qto.Quantities: for prop in qto.Quantities:
@@ -1438,11 +1413,6 @@ class IfcImporter:
new_prop.name = prop.Name new_prop.name = prop.Name
new_prop.string_value = str(value) new_prop.string_value = str(value)
def add_defines_by_type_relation(self, element, obj):
related_type = ifcopenshell.util.element.get_type(element)
if related_type:
obj.BIMObjectProperties.relating_type = self.type_products[related_type.GlobalId]
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
@@ -1499,7 +1469,7 @@ class IfcImporter:
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
unit = unit.ConversionFactor.UnitComponent unit = unit.ConversionFactor.UnitComponent
if unit.is_a("IfcSIUnit"): if unit.is_a("IfcSIUnit"):
self.unit_scale *= helper.SIUnitHelper.get_prefix_multiplier(unit.Prefix) self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
def set_units(self): def set_units(self):
units = self.file.by_type("IfcUnitAssignment")[0] units = self.file.by_type("IfcUnitAssignment")[0]
@@ -1530,28 +1500,6 @@ class IfcImporter:
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
) )
def create_geometric_representation_contexts(self):
bpy.context.scene.BIMProperties.has_model_context = False
for context in self.file.by_type("IfcGeometricRepresentationContext"):
if context.is_a("IfcGeometricRepresentationSubContext"):
if not context.ContextIdentifier:
# Revit creates invalid contexts, so we just ignore them
continue
if context.ContextType == "Model":
subcontexts = bpy.context.scene.BIMProperties.model_subcontexts
elif context.ContextType == "Plan":
subcontexts = bpy.context.scene.BIMProperties.plan_subcontexts
if subcontexts.get(context.ContextIdentifier):
continue
subcontext = subcontexts.add()
subcontext.name = context.ContextIdentifier
subcontext.target_view = context.TargetView
subcontext.ifc_definition_id = context.id()
elif context.ContextType == "Model":
bpy.context.scene.BIMProperties.has_model_context = True
elif context.ContextType == "Plan":
bpy.context.scene.BIMProperties.has_plan_context = True
def create_project(self): def create_project(self):
self.project = {"ifc": self.file.by_type("IfcProject")[0]} self.project = {"ifc": self.file.by_type("IfcProject")[0]}
if self.project["ifc"].GlobalId in self.existing_elements: if self.project["ifc"].GlobalId in self.existing_elements:
@@ -1743,30 +1691,23 @@ class IfcImporter:
] ]
else: else:
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
for collection in self.project["blender"].children:
if collection.name == "Aggregates":
self.aggregate_collection = collection
break
if not self.aggregate_collection:
self.aggregate_collection = bpy.data.collections.new("Aggregates")
self.project["blender"].children.link(self.aggregate_collection)
for rel_aggregate in rel_aggregates: for rel_aggregate in rel_aggregates:
self.create_aggregate(rel_aggregate) self.create_aggregate(rel_aggregate)
def create_aggregate(self, rel_aggregate): def create_aggregate(self, rel_aggregate):
collection = bpy.data.collections.new(f"IfcRelAggregates/{rel_aggregate.id()}")
self.aggregate_collection.children.link(collection)
element = rel_aggregate.RelatingObject element = rel_aggregate.RelatingObject
obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None) obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None)
obj.instance_type = "COLLECTION" obj.BIMObjectProperties.ifc_definition_id = element.id()
obj.instance_collection = collection
self.place_object_in_spatial_tree(element, obj) self.place_object_in_spatial_tree(element, obj)
self.add_element_attributes(element, obj.BIMObjectProperties)
collection = bpy.data.collections.new(obj.name)
obj.users_collection[0].children.link(collection)
obj.users_collection[0].objects.unlink(obj)
collection.objects.link(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_defines_by_type_relation(element, obj)
self.add_product_definitions(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
@@ -1823,12 +1764,11 @@ class IfcImporter:
return return
obj = bpy.data.objects.new(self.get_name(element), mesh) obj = bpy.data.objects.new(self.get_name(element), mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
self.material_creator.create(element, obj, mesh) self.material_creator.create(element, obj, mesh)
obj.matrix_world = self.get_element_matrix(element, mesh_name) obj.matrix_world = self.get_element_matrix(element, mesh_name)
self.add_element_attributes(element, obj.BIMObjectProperties)
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_defines_by_type_relation(element, obj)
self.add_product_definitions(element, obj) self.add_product_definitions(element, obj)
self.added_data[element.GlobalId] = obj self.added_data[element.GlobalId] = obj
@@ -1869,8 +1809,6 @@ class IfcImporter:
): ):
container = element.ContainedInStructure[0].RelatingStructure container = element.ContainedInStructure[0].RelatingStructure
if container.is_a("IfcSpace"): if container.is_a("IfcSpace"):
if self.ifc_import_settings.should_import_spaces and container.GlobalId in self.added_data:
obj.BIMObjectProperties.relating_structure = self.added_data[container.GlobalId]
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)
@@ -2052,23 +1990,6 @@ class IfcImporter:
): ):
return representation.Items[0].MappingTarget return representation.Items[0].MappingTarget
def get_geometry_type(self, element):
tree = []
if hasattr(element, "Representation"):
tree = self.file.traverse(element.Representation)
elif hasattr(element, "RepresentationMaps"):
for representation_map in element.RepresentationMaps:
tree.extend(self.file.traverse(representation_map))
representations = [
e
for e in tree
if e.is_a("IfcRepresentation")
and e.RepresentationIdentifier == "Body"
and e.RepresentationType != "MappedRepresentation"
]
for representation in representations:
return representation.Items[0].is_a()
def create_mesh(self, element, shape, is_curve=False): def create_mesh(self, element, shape, is_curve=False):
try: try:
if hasattr(shape, "geometry"): if hasattr(shape, "geometry"):
@@ -2079,7 +2000,13 @@ class IfcImporter:
if is_curve: if is_curve:
return self.create_curve(geometry) return self.create_curve(geometry)
mesh = bpy.data.meshes.new(geometry.id) representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
mesh = bpy.data.meshes.new("{}/{}".format(
self.file.by_id(representation_id).ContextOfItems.id(), geometry.id))
if geometry.faces: if geometry.faces:
num_vertices = len(geometry.verts) // 3 num_vertices = len(geometry.verts) // 3
@@ -2120,7 +2047,6 @@ class IfcImporter:
ios_materials.append(mat.name) ios_materials.append(mat.name)
mesh["ios_materials"] = ios_materials mesh["ios_materials"] = ios_materials
mesh["ios_material_ids"] = geometry.material_ids mesh["ios_material_ids"] = geometry.material_ids
mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element))
return mesh return mesh
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)
File diff suppressed because it is too large Load Diff
+118 -119
View File
@@ -1,7 +1,6 @@
import json import json
import os import os
import ifcopenshell import ifcopenshell
import ifcopenshell.util.pset
from pathlib import Path from pathlib import Path
from . import export_ifc from . import export_ifc
from . import schema from . import schema
@@ -9,6 +8,7 @@ from . import ifc
from . import annotation from . import annotation
from . import decoration from . import decoration
import bpy import bpy
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from bpy.props import ( from bpy.props import (
@@ -40,35 +40,19 @@ psettemplatefiles_enum = []
propertysettemplates_enum = [] propertysettemplates_enum = []
classification_enum = [] classification_enum = []
attributes_enum = [] attributes_enum = []
psetnames_enum = [] psetnames = {}
qtonames_enum = [] qtonames_enum = []
materialattributes_enum = [] materialattributes_enum = []
materialtypes_enum = [] materialtypes_enum = []
contexts_enum = [] contexts_enum = []
subcontexts_enum = [] subcontexts_enum = []
target_views_enum = [] target_views_enum = []
persons_enum = []
organisations_enum = []
sheets_enum = [] sheets_enum = []
vector_styles_enum = [] vector_styles_enum = []
@persistent @persistent
def setDefaultProperties(scene): def setDefaultProperties(scene):
if (
bpy.context.scene.BIMProperties.has_model_context
and len(bpy.context.scene.BIMProperties.model_subcontexts) == 0
):
subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add()
subcontext.name = "Body"
subcontext.target_view = "MODEL_VIEW"
subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add()
subcontext.name = "Box"
subcontext.target_view = "MODEL_VIEW"
if bpy.context.scene.BIMProperties.has_plan_context and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0:
subcontext = bpy.context.scene.BIMProperties.plan_subcontexts.add()
subcontext.name = "Annotation"
subcontext.target_view = "PLAN_VIEW"
if len(bpy.context.scene.DocProperties.drawing_styles) == 0: if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Technical" drawing_style.name = "Technical"
@@ -140,14 +124,13 @@ def setDefaultProperties(scene):
def getIfcPredefinedTypes(self, context): def getIfcPredefinedTypes(self, context):
global types_enum global types_enum
if len(types_enum) < 1: file = IfcStore.get_file()
for name, data in schema.ifc.elements.items(): if len(types_enum) < 1 and file:
if name != self.ifc_class.strip(): declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
continue for attribute in declaration.attributes():
for attribute in data["attributes"]: if attribute.name() == "PredefinedType":
if attribute["name"] != "PredefinedType": types_enum.extend([(e, e, "") for e in attribute.type_of_attribute().declared_type().enumeration_items()])
continue break
types_enum.extend([(e, e, "") for e in attribute["enum_values"]])
return types_enum return types_enum
@@ -263,6 +246,7 @@ def refreshActiveDrawingIndex(self, context):
def getIfcProducts(self, context): def getIfcProducts(self, context):
global products_enum global products_enum
file = IfcStore.get_file()
if len(products_enum) < 1: if len(products_enum) < 1:
products_enum.extend( products_enum.extend(
[ [
@@ -272,23 +256,38 @@ def getIfcProducts(self, context):
"IfcElementType", "IfcElementType",
"IfcSpatialElement", "IfcSpatialElement",
"IfcGroup", "IfcGroup",
"IfcStructural", "IfcStructuralItem",
"IfcPositioningElement",
"IfcContext", "IfcContext",
"IfcAnnotation", "IfcAnnotation",
] ]
] ]
) )
if file.schema == "IFC2X3":
products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "")
return products_enum return products_enum
def getIfcClasses(self, context): def getIfcClasses(self, context):
global classes_enum global classes_enum
if len(classes_enum) < 1: file = IfcStore.get_file()
classes_enum.extend([(e, e, "") for e in getattr(schema.ifc, self.ifc_product)]) 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 return classes_enum
def getAttributeEnumValues(self, context):
return [(e, e, "") for e in json.loads(self.enum_items)]
def getProfileDef(self, context): def getProfileDef(self, context):
global profiledef_enum global profiledef_enum
if len(profiledef_enum) < 1: if len(profiledef_enum) < 1:
@@ -297,17 +296,27 @@ def getProfileDef(self, context):
def getPersons(self, context): def getPersons(self, context):
global persons_enum from blenderbim.bim.module.owner.data import Data
persons_enum.clear() if not Data.is_loaded:
persons_enum.extend([(p.name, p.name, "") for p in bpy.context.scene.BIMProperties.people]) Data.load()
return persons_enum results = []
for ifc_id, person in Data.people.items():
if "Id" in person:
identifier = person["Id"] or ""
else:
identifier = person["Identifier"] or ""
results.append((str(ifc_id), identifier, ""))
return results
def getOrganisations(self, context): def getOrganisations(self, context):
global organisations_enum from blenderbim.bim.module.owner.data import Data
organisations_enum.clear() if not Data.is_loaded:
organisations_enum.extend([(o.name, o.name, "") for o in bpy.context.scene.BIMProperties.organisations]) Data.load()
return organisations_enum results = []
for ifc_id, organisation in Data.organisations.items():
results.append((str(ifc_id), organisation["Name"], ""))
return results
def getIfcPatchRecipes(self, context): def getIfcPatchRecipes(self, context):
@@ -359,11 +368,18 @@ def refreshTitleblocks(self, context):
def toggleDecorations(self, context): def toggleDecorations(self, context):
toggle = self.should_draw_decorations toggle = self.should_draw_decorations
if toggle: if toggle:
for dec in decoration.all_decorators: decoration.DecorationsHandler.install(context)
dec.install(self, context)
else: else:
for dec in decoration.all_decorators: decoration.DecorationsHandler.uninstall()
dec.uninstall()
@persistent
def toggleDecorationsOnLoad(*args):
toggle = bpy.context.scene.DocProperties.should_draw_decorations
if toggle:
decoration.DecorationsHandler.install(bpy.context)
else:
decoration.DecorationsHandler.uninstall()
def getScenarios(self, context): def getScenarios(self, context):
@@ -430,22 +446,20 @@ def refreshReferences(self, context):
def getPsetNames(self, context): def getPsetNames(self, context):
global psetnames_enum global psetnames
psetnames_enum.clear() if "/" in context.active_object.name:
if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: ifc_class = context.active_object.name.split("/")[0]
pset_names = ifcopenshell.util.pset.get_applicable_psetqtos( if ifc_class not in psetnames:
bpy.context.scene.BIMProperties.export_schema, context.active_object.name.split("/")[0], is_pset=True psets = schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
) psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
psetnames_enum.extend([(p, p, "") for p in pset_names]) return psetnames[ifc_class]
return psetnames_enum return []
def getMaterialPsetNames(self, context): def getMaterialPsetNames(self, context):
global materialpsetnames_enum global materialpsetnames_enum
materialpsetnames_enum.clear() materialpsetnames_enum.clear()
pset_names = ifcopenshell.util.pset.get_applicable_psetqtos( pset_names = schema.ifc.psetqto.get_applicable_names("IfcMaterial", pset_only=True)
bpy.context.scene.BIMProperties.export_schema, "IfcMaterial", is_pset=True
)
materialpsetnames_enum.extend([(p, p, "") for p in pset_names]) materialpsetnames_enum.extend([(p, p, "") for p in pset_names])
return materialpsetnames_enum return materialpsetnames_enum
@@ -454,27 +468,11 @@ def getQtoNames(self, context):
global qtonames_enum global qtonames_enum
qtonames_enum.clear() qtonames_enum.clear()
if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements:
qto_names = ifcopenshell.util.pset.get_applicable_psetqtos( qto_names = schema.ifc.psetqto.get_applicable_names(context.active_object.name.split("/")[0], qto_only=True)
bpy.context.scene.BIMProperties.export_schema, context.active_object.name.split("/")[0], is_qto=True
)
qtonames_enum.extend([(q, q, "") for q in qto_names]) qtonames_enum.extend([(q, q, "") for q in qto_names])
return qtonames_enum return qtonames_enum
def getApplicableAttributes(self, context):
global attributes_enum
attributes_enum.clear()
if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements:
attributes_enum.extend(
[
(a["name"], a["name"], "")
for a in schema.ifc.elements[context.active_object.name.split("/")[0]]["attributes"]
if self.attributes.find(a["name"]) == -1
]
)
return attributes_enum
def getApplicableMaterialAttributes(self, context): def getApplicableMaterialAttributes(self, context):
global materialattributes_enum global materialattributes_enum
materialattributes_enum.clear() materialattributes_enum.clear()
@@ -508,6 +506,19 @@ def getMaterialTypes(self, context):
return materialtypes_enum return materialtypes_enum
def getContexts(self, context):
from blenderbim.bim.module.context.data import Data
if not Data.is_loaded:
Data.load()
results = []
for ifc_id, context in Data.contexts.items():
results.append((str(ifc_id), context["ContextType"], ""))
for ifc_id2, subcontext in context["HasSubContexts"].items():
results.append((str(ifc_id2), "{}/{}/{}".format(
subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"]), ""))
return results
def getSubcontexts(self, context): def getSubcontexts(self, context):
global subcontexts_enum global subcontexts_enum
subcontexts_enum.clear() subcontexts_enum.clear()
@@ -556,19 +567,9 @@ class Attribute(PropertyGroup):
bool_value: BoolProperty(name="Value") bool_value: BoolProperty(name="Value")
int_value: IntProperty(name="Value") int_value: IntProperty(name="Value")
float_value: FloatProperty(name="Value") float_value: FloatProperty(name="Value")
is_null: BoolProperty(name="Is Null")
enum_items: StringProperty(name="Value")
class Subcontext(PropertyGroup): enum_value: EnumProperty(items=getAttributeEnumValues, name="Value")
name: StringProperty(name="Name")
context: StringProperty(name="Context")
target_view: StringProperty(name="Target View")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class Representation(PropertyGroup):
name: StringProperty(name="Name")
type: StringProperty(name="Type")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class MaterialLayer(PropertyGroup): class MaterialLayer(PropertyGroup):
@@ -685,8 +686,7 @@ class DocProperties(PropertyGroup):
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations) should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1),
min=0.0, max=1.0, size=4, min=0.0, max=1.0, size=4)
update=toggleDecorations)
class BIMCameraProperties(PropertyGroup): class BIMCameraProperties(PropertyGroup):
@@ -1097,6 +1097,7 @@ class Address(PropertyGroup):
name: StringProperty(name="Name", default="IfcPostalAddress") # Stores IfcPostalAddress or IfcTelecomAddress name: StringProperty(name="Name", default="IfcPostalAddress") # Stores IfcPostalAddress or IfcTelecomAddress
purpose: EnumProperty( purpose: EnumProperty(
items=[ items=[
("None", "None", ""),
("OFFICE", "OFFICE", "An office address."), ("OFFICE", "OFFICE", "An office address."),
("SITE", "SITE", "A site address."), ("SITE", "SITE", "A site address."),
("HOME", "HOME", "A home address."), ("HOME", "HOME", "A home address."),
@@ -1117,7 +1118,7 @@ class Address(PropertyGroup):
country: StringProperty(name="Country") country: StringProperty(name="Country")
telephone_numbers: StringProperty(name="Telephone Numbers") telephone_numbers: StringProperty(name="Telephone Numbers")
fascimile_numbers: StringProperty(name="Fascimile Numbers") facsimile_numbers: StringProperty(name="Facsimile Numbers")
pager_number: StringProperty(name="Pager Number") pager_number: StringProperty(name="Pager Number")
electronic_mail_addresses: StringProperty(name="Emails") electronic_mail_addresses: StringProperty(name="Emails")
www_home_page_url: StringProperty(name="Websites") www_home_page_url: StringProperty(name="Websites")
@@ -1158,12 +1159,9 @@ class Role(PropertyGroup):
class Organisation(PropertyGroup): class Organisation(PropertyGroup):
identification: StringProperty(name="Identification")
name: StringProperty(name="Name") name: StringProperty(name="Name")
description: StringProperty(name="Description") description: StringProperty(name="Description")
roles: CollectionProperty(name="Roles", type=Role)
active_role_index: bpy.props.IntProperty()
addresses: CollectionProperty(name="Addresses", type=Address)
active_address_index: bpy.props.IntProperty()
class Person(PropertyGroup): class Person(PropertyGroup):
@@ -1173,10 +1171,6 @@ class Person(PropertyGroup):
middle_names: StringProperty(name="Middle Names") middle_names: StringProperty(name="Middle Names")
prefix_titles: StringProperty(name="Prefixes") prefix_titles: StringProperty(name="Prefixes")
suffix_titles: StringProperty(name="Suffixes") suffix_titles: StringProperty(name="Suffixes")
roles: CollectionProperty(name="Roles", type=Role)
active_role_index: bpy.props.IntProperty()
addresses: CollectionProperty(name="Addresses", type=Address)
active_address_index: bpy.props.IntProperty()
class Classification(PropertyGroup): class Classification(PropertyGroup):
@@ -1269,7 +1263,7 @@ class BIMProperties(PropertyGroup):
) )
export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False) export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False)
export_should_force_triangulation: BoolProperty(name="Export with Triangulation", default=False) export_should_force_triangulation: BoolProperty(name="Export with Triangulation", default=False)
export_should_export_from_memory: BoolProperty(name="Export from Memory", default=False) export_should_export_from_memory: BoolProperty(name="Export from Memory", default=True)
import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False) import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False)
import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False) import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False)
import_should_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False) import_should_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False)
@@ -1281,7 +1275,7 @@ class BIMProperties(PropertyGroup):
import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True) import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True)
import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False) import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False)
import_should_import_native: BoolProperty(name="Import Native Representations", default=False) import_should_import_native: BoolProperty(name="Import Native Representations", default=False)
import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=False) import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=True)
import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True)
import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True) import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True)
import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True) import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True)
@@ -1296,12 +1290,18 @@ class BIMProperties(PropertyGroup):
import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False) import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False)
import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0") import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0")
qa_reject_element_reason: StringProperty(name="Element Rejection Reason") qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
person: EnumProperty(items=getPersons, name="Person")
organisation: EnumProperty(items=getOrganisations, name="Organisation") person: PointerProperty(type=Person)
people: CollectionProperty(name="People", type=Person) active_person_id: IntProperty(name="Active Person Id")
organisations: CollectionProperty(name="Organisations", type=Organisation) organisation: PointerProperty(type=Organisation)
active_person_index: IntProperty(name="Active Person Index") active_organisation_id: IntProperty(name="Active Organisation Id")
active_organisation_index: IntProperty(name="Active Organisation Index") role: PointerProperty(type=Role)
active_role_id: IntProperty(name="Active Role Id")
address: PointerProperty(type=Address)
active_address_id: IntProperty(name="Active Address Id")
user_person: EnumProperty(items=getPersons, name="Person")
user_organisation: EnumProperty(items=getOrganisations, name="Organisation")
has_georeferencing: BoolProperty(name="Has Georeferencing", default=False) has_georeferencing: BoolProperty(name="Has Georeferencing", default=False)
has_library: BoolProperty(name="Has Project Library", default=False) has_library: BoolProperty(name="Has Project Library", default=False)
search_regex: BoolProperty(name="Search With Regex", default=False) search_regex: BoolProperty(name="Search With Regex", default=False)
@@ -1328,10 +1328,7 @@ class BIMProperties(PropertyGroup):
classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences)
active_classification_name: StringProperty(name="Active Classification Name") active_classification_name: StringProperty(name="Active Classification Name")
classifications: CollectionProperty(name="Classifications", type=Classification) classifications: CollectionProperty(name="Classifications", type=Classification)
has_model_context: BoolProperty(name="Has Model Context", default=True) contexts: EnumProperty(items=getContexts, name="Contexts")
has_plan_context: BoolProperty(name="Has Plan Context", default=True)
model_subcontexts: CollectionProperty(name="Model Subcontexts", type=Subcontext)
plan_subcontexts: CollectionProperty(name="Plan Subcontexts", type=Subcontext)
available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts") available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts")
available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts") available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts")
available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views") available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views")
@@ -1487,14 +1484,23 @@ class BoundaryCondition(PropertyGroup):
class BIMObjectProperties(PropertyGroup): class BIMObjectProperties(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_reassigning_class: BoolProperty(name="Is Reassigning Class") is_reassigning_class: BoolProperty(name="Is Reassigning Class")
global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) global_ids: CollectionProperty(name="GlobalIds", type=GlobalId)
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object)
is_editing_aggregate: BoolProperty(name="Is Editing Aggregate")
is_editing_container: BoolProperty(name="Is Editing Container")
relating_type: PointerProperty(name="Type", type=bpy.types.Object)
is_editing_type: BoolProperty(name="Is Editing Type")
relating_type: PointerProperty(name="Type Product", type=bpy.types.Object) relating_type: PointerProperty(name="Type Product", type=bpy.types.Object)
relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object) relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object)
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
psets: CollectionProperty(name="Psets", type=PsetQto) psets: CollectionProperty(name="Psets", type=PsetQto)
qtos: CollectionProperty(name="Qtos", type=PsetQto) qtos: CollectionProperty(name="Qtos", type=PsetQto)
applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names")
document_references: CollectionProperty(name="Document References", type=DocumentReference) document_references: CollectionProperty(name="Document References", type=DocumentReference)
active_document_reference_index: IntProperty(name="Active Document Reference Index") active_document_reference_index: IntProperty(name="Active Document Reference Index")
constraints: CollectionProperty(name="Constraints", type=Constraint) constraints: CollectionProperty(name="Constraints", type=Constraint)
@@ -1508,20 +1514,10 @@ class BIMObjectProperties(PropertyGroup):
has_boundary_condition: BoolProperty(name="Has Boundary Condition") has_boundary_condition: BoolProperty(name="Has Boundary Condition")
boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition)
structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object)
representations: CollectionProperty(name="Representations", type=Representation)
# Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress # Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress
address: PointerProperty(name="Address", type=Address) address: PointerProperty(name="Address", type=Address)
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)
class BIMMaterialProperties(PropertyGroup): class BIMMaterialProperties(PropertyGroup):
is_external: BoolProperty(name="Has External Definition") is_external: BoolProperty(name="Has External Definition")
location: StringProperty(name="Location") location: StringProperty(name="Location")
@@ -1531,6 +1527,9 @@ class BIMMaterialProperties(PropertyGroup):
psets: CollectionProperty(name="Psets", type=PsetQto) psets: CollectionProperty(name="Psets", type=PsetQto)
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
applicable_attributes: EnumProperty(items=getApplicableMaterialAttributes, name="Attribute Names") applicable_attributes: EnumProperty(items=getApplicableMaterialAttributes, name="Attribute Names")
ifc_definition_id: IntProperty(name="IFC Definition ID")
# In Blender, a material object can map to an IFC material, IFC surface style, or both
ifc_style_id: IntProperty(name="IFC Style ID")
class SweptSolid(PropertyGroup): class SweptSolid(PropertyGroup):
@@ -1551,13 +1550,13 @@ class ItemSlotMap(PropertyGroup):
class BIMMeshProperties(PropertyGroup): class BIMMeshProperties(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_native: BoolProperty(name="Is Native", default=False) is_native: BoolProperty(name="Is Native", default=False)
is_swept_solid: BoolProperty(name="Is Swept Solid") is_swept_solid: BoolProperty(name="Is Swept Solid")
swept_solids: CollectionProperty(name="Swept Solids", type=SweptSolid) swept_solids: CollectionProperty(name="Swept Solids", type=SweptSolid)
is_parametric: BoolProperty(name="Is Parametric", default=False) is_parametric: BoolProperty(name="Is Parametric", default=False)
geometry_type: StringProperty(name="Geometry Type")
ifc_definition: StringProperty(name="IFC Definition") ifc_definition: StringProperty(name="IFC Definition")
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
active_representation_item_index: IntProperty(name="Active Representation Item Index") active_representation_item_index: IntProperty(name="Active Representation Item Index")
presentation_layer_index: IntProperty(name="Presentation Layer Index", default=-1) presentation_layer_index: IntProperty(name="Presentation Layer Index", default=-1)
ifc_item_ids: CollectionProperty(name="IFC Definition ID", type=ItemSlotMap) ifc_item_ids: CollectionProperty(name="IFC Item IDs", type=ItemSlotMap)
@@ -10,11 +10,10 @@ cwd = os.path.dirname(os.path.realpath(__file__))
class IfcSchema: class IfcSchema:
def __init__(self): def __init__(self):
self.schema_dir = os.path.join(cwd, "schema") # TODO: make configurable self.schema_dir = Path(cwd).joinpath("schema") # TODO: make configurable
self.data_dir = os.path.join(cwd, "data") # TODO: make configurable self.data_dir = Path(cwd).joinpath("data") # TODO: make configurable
# TODO: Make it less troublesome # TODO: Make it less troublesome
self.products = [ self.products = [
"IfcContext",
"IfcElement", "IfcElement",
"IfcSpatialElement", "IfcSpatialElement",
"IfcGroup", "IfcGroup",
@@ -29,10 +28,11 @@ class IfcSchema:
self.elements = {} self.elements = {}
self.property_files = [] self.property_files = []
property_paths = Path(os.path.join(self.data_dir, "pset")).glob("*.ifc") property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4")
for path in property_paths: for path in property_paths:
ifcopenshell.util.pset.load_property_set_template(path) self.psetqto.templates.append(ifcopenshell.open(path))
ifcopenshell.util.pset.load_property_set_template(os.path.join(self.schema_dir, "Pset_IFC4_ADD2.ifc"))
self.classification_files = {} self.classification_files = {}
self.classifications = {} self.classifications = {}
@@ -1,114 +0,0 @@
{
"IfcProject": {
"is_abstract": false,
"parent": "IfcContext",
"attributes": [
{
"name": "GlobalId",
"type": "IfcGloballyUniqueId",
"is_enum": false,
"enum_values": []
},
{
"name": "Name",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "Description",
"type": "IfcText",
"is_enum": false,
"enum_values": []
},
{
"name": "ObjectType",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "LongName",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "Phase",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
}
],
"complex_attributes": [
{
"name": "OwnerHistory",
"type": "IfcOwnerHistory",
"is_select": false,
"select_types": []
},
{
"name": "UnitsInContext",
"type": "IfcUnitAssignment",
"is_select": false,
"select_types": []
}
]
},
"IfcProjectLibrary": {
"is_abstract": false,
"parent": "IfcContext",
"attributes": [
{
"name": "GlobalId",
"type": "IfcGloballyUniqueId",
"is_enum": false,
"enum_values": []
},
{
"name": "Name",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "Description",
"type": "IfcText",
"is_enum": false,
"enum_values": []
},
{
"name": "ObjectType",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "LongName",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
},
{
"name": "Phase",
"type": "IfcLabel",
"is_enum": false,
"enum_values": []
}
],
"complex_attributes": [
{
"name": "OwnerHistory",
"type": "IfcOwnerHistory",
"is_select": false,
"select_types": []
},
{
"name": "UnitsInContext",
"type": "IfcUnitAssignment",
"is_select": false,
"select_types": []
}
]
}
}
File diff suppressed because it is too large Load Diff
+1 -561
View File
@@ -5,106 +5,6 @@ from bpy.types import Panel
from bpy.props import StringProperty from bpy.props import StringProperty
class BIM_PT_object(Panel):
bl_label = "IFC Object"
bl_idname = "BIM_PT_object"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties")
def draw(self, context):
if context.active_object is None:
return
layout = self.layout
props = context.active_object.BIMObjectProperties
bim_properties = context.scene.BIMProperties
if props.is_reassigning_class or "/" not in context.active_object.name:
row = layout.row()
row.prop(bim_properties, "ifc_product")
row = layout.row()
row.prop(bim_properties, "ifc_class")
if bim_properties.ifc_predefined_type:
row = layout.row()
row.prop(bim_properties, "ifc_predefined_type")
if bim_properties.ifc_predefined_type == "USERDEFINED":
row = layout.row()
row.prop(bim_properties, "ifc_userdefined_type")
row = layout.row(align=True)
if "Ifc" not in context.active_object.name:
op = row.operator("bim.assign_class")
else:
op = row.operator("bim.assign_class")
op.object_name = context.active_object.name
op = row.operator("bim.unassign_class", icon="X", text="")
op.object_name = context.active_object.name
else:
row = layout.row()
row.operator("bim.reassign_class", text="Reassign IFC Class")
if "Ifc" not in context.active_object.name:
return
layout.label(text="Attributes:")
row = layout.row(align=True)
row.prop(props, "applicable_attributes", text="")
row.operator("bim.add_attribute")
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.name == "GlobalId":
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
op = row.operator("bim.copy_attribute_to_selection", icon="COPYDOWN", text="")
op.attribute_name = attribute.name
op.attribute_value = attribute.string_value
row.operator("bim.remove_attribute", icon="X", text="").attribute_index = index
row = layout.row()
row.prop(props, "attributes")
if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
self.draw_addresses_ui()
row = layout.row(align=True)
row.prop(props, "relating_type")
row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
row = layout.row()
row.prop(props, "relating_structure")
def draw_addresses_ui(self):
layout = self.layout
layout.label(text="Address:")
address = bpy.context.active_object.BIMObjectProperties.address
row = layout.row()
row.prop(address, "purpose")
if address.purpose == "USERDEFINED":
row = layout.row()
row.prop(address, "user_defined_purpose")
row = layout.row()
row.prop(address, "description")
row = layout.row()
row.prop(address, "internal_location")
row = layout.row()
row.prop(address, "address_lines")
row = layout.row()
row.prop(address, "postal_box")
row = layout.row()
row.prop(address, "town")
row = layout.row()
row.prop(address, "region")
row = layout.row()
row.prop(address, "postal_code")
row = layout.row()
row.prop(address, "country")
class BIM_PT_object_material(Panel): class BIM_PT_object_material(Panel):
bl_label = "IFC Object Material" bl_label = "IFC Object Material"
bl_idname = "BIM_PT_object_material" bl_idname = "BIM_PT_object_material"
@@ -243,73 +143,6 @@ class BIM_PT_object_material(Panel):
row.prop(attribute, "string_value", text="") row.prop(attribute, "string_value", text="")
class BIM_PT_object_psets(Panel):
bl_label = "IFC Object Property Sets"
bl_idname = "BIM_PT_object_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties")
def draw(self, context):
if context.active_object is None:
return
layout = self.layout
props = context.active_object.BIMObjectProperties
row = layout.row(align=True)
row.prop(props, "pset_name", text="")
row.operator("bim.add_pset")
self.draw_psets_ui(props)
if props.relating_type and props.relating_type.BIMObjectProperties.psets:
layout.label(text="Inherited Psets:")
self.draw_psets_ui(props.relating_type.BIMObjectProperties, enabled=False)
def draw_psets_ui(self, props, enabled=True):
for index, pset in enumerate(props.psets):
box = self.layout.box()
row = box.row(align=True)
row.prop(
pset,
"is_expanded",
icon="TRIA_DOWN" if pset.is_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False,
)
row2 = row.row(align=True)
row2.enabled = enabled
row2.prop(pset, "name", text="", icon="COPY_ID", emboss=pset.is_editable)
if enabled:
row.prop(pset, "is_editable", icon="CHECKMARK" if pset.is_editable else "GREASEPENCIL", icon_only=True)
row.operator("bim.remove_pset", icon="X", text="").pset_index = index
if not pset.is_expanded:
continue
if pset.is_editable:
for prop in pset.properties:
row = box.row(align=True)
row.enabled = enabled
row.prop(prop, "name", text="")
row.prop(prop, "string_value", text="")
if not enabled:
continue
op = row.operator("bim.copy_property_to_selection", icon="COPYDOWN", text="")
op.pset_name = pset.name
op.prop_name = prop.name
op.prop_value = prop.string_value
else:
col = box.column(align=True)
for prop in pset.properties:
if not prop.string_value:
continue
col.enabled = enabled
col.prop(prop, "string_value", text=prop.name)
class BIM_PT_object_qto(Panel): class BIM_PT_object_qto(Panel):
bl_label = "IFC Object Quantity Sets" bl_label = "IFC Object Quantity Sets"
bl_idname = "BIM_PT_object_qto" bl_idname = "BIM_PT_object_qto"
@@ -626,48 +459,6 @@ class BIM_PT_constraint_relations(Panel):
layout.label(text="Constraint is invalid") layout.label(text="Constraint is invalid")
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 not props.representations:
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(bpy.context.scene.BIMProperties, "available_contexts", text="")
row.prop(bpy.context.scene.BIMProperties, "available_subcontexts", text="")
row.prop(bpy.context.scene.BIMProperties, "available_target_views", text="")
# TODO: reimplement with incremental editing
# op = row.operator("bim.switch_context", icon="ADD", text="")
# op.has_target_context = False
self.file = ifc.IfcStore.get_file()
for index, representation in enumerate(props.representations):
row = layout.row(align=True)
row.prop(representation, "name", text="")
row.prop(representation, "type", text="")
if not representation.ifc_definition_id:
continue
subcontext = self.file.by_id(representation.ifc_definition_id).ContextOfItems
if subcontext.is_a() == "IfcGeometricRepresentationContext":
continue
# TODO: reimplement with incremental editing
# op = row.operator("bim.switch_context", icon="OUTLINER_DATA_MESH", text="")
# op.has_target_context = True
# op.context_name = subcontext.ContextType
# op.subcontext_name = subcontext.ContextIdentifier
# op.target_view_name = subcontext.TargetView
# row.operator("bim.remove_context", icon="X", text="").index = index
class BIM_PT_classification_references(Panel): class BIM_PT_classification_references(Panel):
bl_label = "IFC Classification References" bl_label = "IFC Classification References"
bl_idname = "BIM_PT_classification_references" bl_idname = "BIM_PT_classification_references"
@@ -794,70 +585,6 @@ class BIM_PT_classifications(Panel):
row.prop(props, "classifications") row.prop(props, "classifications")
class BIM_PT_mesh(Panel):
bl_label = "IFC Representations"
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(align=True)
row.operator("bim.push_representation")
row = layout.row()
row.prop(props, "geometry_type")
layout.label(text="IFC Parameters:")
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
row = layout.row()
row.operator("bim.bake_parametric_geometry")
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_ifc_representation", icon="FILE_REFRESH", text="").index = index
row = layout.row()
row.prop(props, "is_parametric")
row = layout.row()
row.prop(props, "is_native")
row = layout.row()
row.prop(props, "is_swept_solid")
row = layout.row()
row.operator("bim.add_swept_solid")
for index, swept_solid in enumerate(props.swept_solids):
row = layout.row(align=True)
row.prop(swept_solid, "name", text="")
row.operator("bim.remove_swept_solid", icon="X", text="").index = index
row = layout.row()
sub = row.row(align=True)
sub.operator("bim.assign_swept_solid_outer_curve").index = index
sub.operator("bim.select_swept_solid_outer_curve", icon="RESTRICT_SELECT_OFF", text="").index = index
sub = row.row(align=True)
sub.operator("bim.add_swept_solid_inner_curve").index = index
sub.operator("bim.select_swept_solid_inner_curves", icon="RESTRICT_SELECT_OFF", text="").index = index
row = layout.row(align=True)
row.operator("bim.assign_swept_solid_extrusion").index = index
row.operator("bim.select_swept_solid_extrusion", icon="RESTRICT_SELECT_OFF", text="").index = index
row = layout.row()
row.prop(props, "swept_solids")
class BIM_PT_presentation_layer_data(Panel): class BIM_PT_presentation_layer_data(Panel):
bl_label = "IFC Presentation Layers" bl_label = "IFC Presentation Layers"
bl_idname = "BIM_PT_presentation" bl_idname = "BIM_PT_presentation"
@@ -1303,213 +1030,6 @@ class BIM_PT_text(Panel):
row.prop(variable, "prop_key") row.prop(variable, "prop_key")
class BIM_PT_owner(Panel):
bl_label = "IFC Owner History"
bl_idname = "BIM_PT_owner"
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.BIMProperties
if not props.person:
layout.label(text="No people found.")
else:
row = layout.row()
row.prop(props, "person")
if not props.organisation:
layout.label(text="No organisations found.")
else:
row = layout.row()
row.prop(props, "organisation")
class BIM_PT_people(Panel):
bl_label = "IFC People"
bl_idname = "BIM_PT_people"
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
props = context.scene.BIMProperties
row = layout.row()
row.operator("bim.add_person")
if props.people:
layout.template_list("BIM_UL_generic", "", props, "people", props, "active_person_index")
if props.active_person_index < len(props.people):
person = props.people[props.active_person_index]
row = layout.row()
row.prop(person, "name")
row.operator("bim.remove_person", icon="X", text="").index = props.active_person_index
row = layout.row()
row.prop(person, "family_name")
row = layout.row()
row.prop(person, "given_name")
row = layout.row()
row.prop(person, "middle_names")
row = layout.row()
row.prop(person, "prefix_titles")
row = layout.row()
row.prop(person, "suffix_titles")
layout.label(text="Roles:")
draw_roles_ui(layout, person, "person")
layout.label(text="Addresses:")
draw_addresses_ui(layout, person, "person")
class BIM_PT_organisations(Panel):
bl_label = "IFC Organisations"
bl_idname = "BIM_PT_organisations"
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
props = context.scene.BIMProperties
row = layout.row()
row.operator("bim.add_organisation")
if props.organisations:
layout.template_list("BIM_UL_generic", "", props, "organisations", props, "active_organisation_index")
if props.active_organisation_index < len(props.organisations):
organisation = props.organisations[props.active_organisation_index]
row = layout.row()
row.prop(organisation, "name")
row.operator("bim.remove_organisation", icon="X", text="").index = props.active_organisation_index
row = layout.row()
row.prop(organisation, "description")
layout.label(text="Roles:")
draw_roles_ui(layout, organisation, "organisation")
layout.label(text="Addresses:")
draw_addresses_ui(layout, organisation, "organisation")
def draw_roles_ui(layout, parent, parent_type):
row = layout.row()
row.operator(f"bim.add_{parent_type}_role")
if parent.roles:
layout.template_list("BIM_UL_generic", "", parent, "roles", parent, "active_role_index")
if parent.active_role_index < len(parent.roles):
role = parent.roles[parent.active_role_index]
row = layout.row()
row.prop(role, "name")
row.operator(f"bim.remove_{parent_type}_role", icon="X", text="").index = parent.active_role_index
if role.name == "USERDEFINED":
row = layout.row()
row.prop(role, "user_defined_role")
row = layout.row()
row.prop(role, "description")
def draw_addresses_ui(layout, parent, parent_type):
row = layout.row()
row.operator(f"bim.add_{parent_type}_address")
if parent.addresses:
layout.template_list("BIM_UL_generic", "", parent, "addresses", parent, "active_address_index")
if parent.active_address_index < len(parent.addresses):
address = parent.addresses[parent.active_address_index]
row = layout.row()
row.prop(address, "name")
row.operator(f"bim.remove_{parent_type}_address", icon="X", text="").index = parent.active_address_index
row = layout.row()
row.prop(address, "purpose")
if address.purpose == "USERDEFINED":
row = layout.row()
row.prop(address, "user_defined_purpose")
row = layout.row()
row.prop(address, "description")
if "IfcPostalAddress" in address.name:
row = layout.row()
row.prop(address, "internal_location")
row = layout.row()
row.prop(address, "address_lines")
row = layout.row()
row.prop(address, "postal_box")
row = layout.row()
row.prop(address, "town")
row = layout.row()
row.prop(address, "region")
row = layout.row()
row.prop(address, "postal_code")
row = layout.row()
row.prop(address, "country")
elif "IfcTelecomAddress" in address.name:
row = layout.row()
row.prop(address, "telephone_numbers")
row = layout.row()
row.prop(address, "fascimile_numbers")
row = layout.row()
row.prop(address, "pager_number")
row = layout.row()
row.prop(address, "electronic_mail_addresses")
row = layout.row()
row.prop(address, "www_home_page_url")
row = layout.row()
row.prop(address, "messaging_ids")
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"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.BIMProperties
for context in ["model", "plan"]:
row = layout.row(align=True)
row.prop(props, f"has_{context}_context")
if not getattr(props, f"has_{context}_context"):
continue
layout.label(text="Geometric Representation Subcontexts:")
row = layout.row(align=True)
row.prop(props, "available_subcontexts", text="")
row.prop(props, "available_target_views", text="")
row.operator("bim.add_subcontext", icon="ADD", text="").context = context
for subcontext_index, subcontext in enumerate(getattr(props, "{}_subcontexts".format(context))):
row = layout.row(align=True)
row.prop(subcontext, "name", text="")
row.prop(subcontext, "target_view", text="")
row.operator("bim.remove_subcontext", icon="X", text="").indexes = "{}-{}".format(
context, subcontext_index
)
class BIM_PT_bim(Panel): class BIM_PT_bim(Panel):
bl_label = "Building Information Modeling" bl_label = "Building Information Modeling"
bl_idname = "BIM_PT_bim" bl_idname = "BIM_PT_bim"
@@ -1526,9 +1046,6 @@ class BIM_PT_bim(Panel):
layout.label(text="System Setup:") layout.label(text="System Setup:")
row = layout.row()
row.operator("bim.quick_project_setup")
row = layout.row(align=True) row = layout.row(align=True)
row.prop(bim_properties, "schema_dir") row.prop(bim_properties, "schema_dir")
row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="") row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="")
@@ -1548,22 +1065,6 @@ class BIM_PT_bim(Panel):
layout.label(text="IFC Categorisation:") layout.label(text="IFC Categorisation:")
row = layout.row()
row.prop(bim_properties, "ifc_product")
row = layout.row()
row.prop(bim_properties, "ifc_class")
if bim_properties.ifc_predefined_type:
row = layout.row()
row.prop(bim_properties, "ifc_predefined_type")
if bim_properties.ifc_predefined_type == "USERDEFINED":
row = layout.row()
row.prop(bim_properties, "ifc_userdefined_type")
row = layout.row(align=True)
op = row.operator("bim.assign_class")
op.object_name = ""
op = row.operator("bim.unassign_class", icon="X", text="")
op.object_name = ""
row = layout.row(align=True) row = layout.row(align=True)
row.operator("bim.select_class") row.operator("bim.select_class")
row.operator("bim.select_type") row.operator("bim.select_type")
@@ -1850,6 +1351,7 @@ class BIM_PT_patch(Panel):
class BIM_PT_mvd(Panel): class BIM_PT_mvd(Panel):
bl_label = "Model View Definitions (MVD)" bl_label = "Model View Definitions (MVD)"
bl_idname = "BIM_PT_mvd" bl_idname = "BIM_PT_mvd"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
@@ -2360,68 +1862,6 @@ class BIM_PT_misc_utilities(Panel):
row.operator("bim.snap_spaces_together") row.operator("bim.snap_spaces_together")
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
def ifc_units(self, context): def ifc_units(self, context):
scene = context.scene scene = context.scene
props = context.scene.BIMProperties props = context.scene.BIMProperties
+5
View File
@@ -376,6 +376,8 @@ int main(int argc, char** argv) {
"Creates SVG elevation drawings automatically based on model extents") "Creates SVG elevation drawings automatically based on model extents")
("svg-xmlns", ("svg-xmlns",
"Stores name and guid in a separate namespace as opposed to data-name, data-guid") "Stores name and guid in a separate namespace as opposed to data-name, data-guid")
("svg-poly",
"Uses the polygonal algorithm for hidden line rendering")
("door-arcs", "Draw door openings arcs for IfcDoor elements") ("door-arcs", "Draw door openings arcs for IfcDoor elements")
("section-height", po::value<double>(&section_height), ("section-height", po::value<double>(&section_height),
"Specifies the cut section height for SVG 2D geometry.") "Specifies the cut section height for SVG 2D geometry.")
@@ -467,6 +469,7 @@ int main(int argc, char** argv) {
const bool use_element_types = vmap.count("use-element-types") != 0; const bool use_element_types = vmap.count("use-element-types") != 0;
const bool use_element_hierarchy = vmap.count("use-element-hierarchy") != 0; const bool use_element_hierarchy = vmap.count("use-element-hierarchy") != 0;
const bool use_z_up = vmap.count("use-z-up") != 0; const bool use_z_up = vmap.count("use-z-up") != 0;
const bool no_normals = vmap.count("no-normals") != 0; const bool no_normals = vmap.count("no-normals") != 0;
const bool center_model = vmap.count("center-model") != 0; const bool center_model = vmap.count("center-model") != 0;
const bool center_model_geometry = vmap.count("center-model-geometry") != 0; const bool center_model_geometry = vmap.count("center-model-geometry") != 0;
@@ -741,6 +744,7 @@ int main(int argc, char** argv) {
settings.set(SerializerSettings::USE_ELEMENT_NAMES, use_element_names); settings.set(SerializerSettings::USE_ELEMENT_NAMES, use_element_names);
settings.set(SerializerSettings::USE_ELEMENT_GUIDS, use_element_guids); settings.set(SerializerSettings::USE_ELEMENT_GUIDS, use_element_guids);
settings.set(SerializerSettings::USE_Z_UP, use_z_up); settings.set(SerializerSettings::USE_Z_UP, use_z_up);
settings.set(SerializerSettings::USE_ELEMENT_STEPIDS, use_element_stepids); settings.set(SerializerSettings::USE_ELEMENT_STEPIDS, use_element_stepids);
settings.set(SerializerSettings::USE_MATERIAL_NAMES, use_material_names); settings.set(SerializerSettings::USE_MATERIAL_NAMES, use_material_names);
settings.set(SerializerSettings::USE_ELEMENT_TYPES, use_element_types); settings.set(SerializerSettings::USE_ELEMENT_TYPES, use_element_types);
@@ -962,6 +966,7 @@ int main(int argc, char** argv) {
static_cast<SvgSerializer*>(serializer.get())->setAutoElevation(true); static_cast<SvgSerializer*>(serializer.get())->setAutoElevation(true);
} }
static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0); static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0);
static_cast<SvgSerializer*>(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0);
if (relative_center_x && relative_center_y) { if (relative_center_x && relative_center_y) {
static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y); static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y);
} }
+2 -19
View File
@@ -4,6 +4,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.selector import ifcopenshell.util.selector
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.schema
import csv import csv
import lark import lark
import argparse import argparse
@@ -13,7 +14,7 @@ class IfcAttributeExtractor:
@staticmethod @staticmethod
def set_element_key(ifc_file, element, key, value): def set_element_key(ifc_file, element, key, value):
if key == "type" and element.is_a() != value: if key == "type" and element.is_a() != value:
return IfcAttributeExtractor.change_ifc_class(ifc_file, element, value) return ifcopenshell.util.schema.reassign_class(ifc_file, element, value)
if hasattr(element, key): if hasattr(element, key):
setattr(element, key, value) setattr(element, key, value)
return element return element
@@ -32,24 +33,6 @@ class IfcAttributeExtractor:
return element return element
return element return element
@staticmethod
def change_ifc_class(ifc_file, element, new_class):
try:
new_element = ifc_file.create_entity(new_class)
except:
print(f"Class of {element} could not be changed to {new_class}")
return element
new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)]
for i, attribute in enumerate(element):
try:
new_element[new_attributes.index(element.attribute_name(i))] = attribute
except:
continue
for inverse in ifc_file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
ifc_file.remove(element)
return new_element
@staticmethod @staticmethod
def get_element_qto(element, name): def get_element_qto(element, name):
for relationship in element.IsDefinedBy: for relationship in element.IsDefinedBy:
@@ -63,8 +63,8 @@ def open(fn):
raise IOError("Unable to open file for reading") raise IOError("Unable to open file for reading")
def create_entity(type, *args, **kwargs): def create_entity(type, schema='IFC4', *args, **kwargs):
e = entity_instance(type) e = entity_instance((schema, type))
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()] attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
for idx, arg in attrs: for idx, arg in attrs:
e[idx] = arg e[idx] = arg
@@ -271,3 +271,10 @@ class entity_instance(object):
return return_type(_()) return return_type(_())
__dict__ = property(get_info) __dict__ = property(get_info)
def get_info_2(self, include_identifier=True, recursive=False, return_type=dict, ignore=()):
assert include_identifier
assert recursive
assert return_type is dict
assert len(ignore) == 0
return ifcopenshell_wrapper.get_info_cpp(self.wrapped_data)
@@ -93,3 +93,32 @@ def replace_attribute(element, old, new):
if item == old: if item == old:
new_attribute[j] = new new_attribute[j] = new
element[i] = new_attribute element[i] = new_attribute
def is_representation_of_context(representation, context, subcontext=None, target_view=None):
if target_view is not None:
return (
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
and representation.ContextOfItems.TargetView == target_view
and representation.ContextOfItems.ContextIdentifier == subcontext
and representation.ContextOfItems.ContextType == context
)
elif subcontext is not None:
return (
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
and representation.ContextOfItems.ContextIdentifier == subcontext
and representation.ContextOfItems.ContextType == context
)
elif representation.ContextOfItems.ContextType == context:
return True
def get_representation(element, context, subcontext=None, target_view=None):
if element.is_a("IfcProduct") and element.Representation:
for r in element.Representation.Representations:
if is_representation_of_context(r, context, subcontext, target_view):
return r
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
for r in element.RepresentationMaps:
if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view):
return r.MappedRepresentation
@@ -1,39 +1,77 @@
import pathlib
import re
from functools import lru_cache
from typing import List, Generator, Optional
import ifcopenshell import ifcopenshell
from ifcopenshell.entity_instance import entity_instance
property_set_template_files = []
psets = {}
qtos = {}
applicable_psets = {}
applicable_qtos = {}
def load_property_set_template(path): class PsetQto:
property_set_template_files.append(ifcopenshell.open(path)) templates_path = {
for prop in property_set_template_files[-1].by_type("IfcPropertySetTemplate"): "IFC4": "Pset_IFC4_ADD2.ifc",
if prop.Name[0:4] == "Qto_": }
qtos[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
applicable_qtos.setdefault(entity, []).append(prop.Name)
else:
psets[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
applicable_psets.setdefault(entity, []).append(prop.Name)
def __init__(self, schema: str, templates=None) -> None:
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
if not templates:
folder_path = pathlib.Path(__file__).parent.absolute()
path = folder_path.joinpath("schema", self.templates_path[schema])
templates = [ifcopenshell.open(path)]
self.templates = templates
def get_applicable_psetqtos(schema_version, ifc_class, is_pset=False, is_qto=False): @lru_cache()
def is_a(entity, ifc_class): def get_applicable(
if entity.name() == ifc_class: self, ifc_class="", predefined_type="", pset_only=False, qto_only=False
return True ) -> Generator[entity_instance, entity_instance, None]:
return is_a(entity.supertype(), ifc_class) if entity.supertype() else False any_class = not ifc_class
if not any_class:
entity = self.schema.declaration_by_name(ifc_class)
for template in self.templates:
for prop_set in template.by_type("IfcPropertySetTemplate"):
if pset_only:
if prop_set.Name.startswith("Qto_"):
continue
if qto_only:
if not prop_set.Name.startswith("Qto_"):
continue
if any_class or self.is_applicable(entity, prop_set.ApplicableEntity or "IfcRoot", predefined_type):
yield prop_set
results = [] def get_applicable_names(self, ifc_class: str, predefined_type="", pset_only=False, qto_only=False) -> List[str]:
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_version) """Return names instead of objects for other use eg. enum"""
entity = schema.declaration_by_name(ifc_class) return [prop_set.Name for prop_set in self.get_applicable(ifc_class, predefined_type, pset_only, qto_only)]
if is_pset:
search_items = applicable_psets.items() def is_applicable(self, entity: entity_instance, applicables: str, predefined_type="") -> bool:
elif is_qto: """applicables can have multiple possible patterns :
search_items = applicable_qtos.items() IfcBoilerType (IfcClass)
for ifc_class, pset_names in search_items: IfcBoilerType/STEAM (IfcClass/PREDEFINEDTYPE)
if is_a(entity, ifc_class): IfcBoilerType[PerformanceHistory] (IfcClass[PerformanceHistory])
results.extend(pset_names) IfcBoilerType/STEAM[PerformanceHistory] (IfcClass/PREDEFINEDTYPE[PerformanceHistory])
return results """
for applicable in applicables.split(","):
match = re.match(r"(\w+)(\[\w+\])*/*(\w+)*(\[\w+\])*", applicable)
if not match:
continue
# Uncomment if usage found
# applicable_perf_history = match.group(2) or match.group(4)
if predefined_type and predefined_type != match.group(3):
continue
applicable_class = match.group(1)
if entity.name() == applicable_class:
return True
if entity.supertype():
return self.is_applicable(entity.supertype(), applicable_class)
return False
@lru_cache()
def get_by_name(self, name: str) -> Optional[entity_instance]:
for template in self.templates:
for prop_set in template.by_type("IfcPropertySetTemplate"):
if prop_set.Name == name:
return prop_set
return None
def is_templated(self, name: str) -> bool:
return bool(self.get_by_name(name))
@@ -18,6 +18,25 @@ def is_a(entity, ifc_class):
return False return False
def reassign_class(ifc_file, element, new_class):
try:
new_element = ifc_file.create_entity(new_class)
except:
print(f"Class of {element} could not be changed to {new_class}")
return element
new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)]
for i, attribute in enumerate(element):
try:
new_element[new_attributes.index(element.attribute_name(i))] = attribute
except:
continue
for inverse in ifc_file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
ifc_file.remove(element)
return new_element
class Migrator: class Migrator:
def __init__(self): def __init__(self):
self.migrated_ids = {} self.migrated_ids = {}
@@ -142,3 +142,17 @@ def convert(value, from_prefix, from_unit, to_prefix, to_unit):
value *= 1 / get_prefix_multiplier(to_prefix) value *= 1 / get_prefix_multiplier(to_prefix)
value *= 1 / get_prefix_multiplier(to_prefix) value *= 1 / get_prefix_multiplier(to_prefix)
return value return value
def calculate_unit_scale(file):
units = file.by_type("IfcUnitAssignment")[0]
unit_scale = 1
for unit in units.Units:
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
continue
while unit.is_a("IfcConversionBasedUnit"):
unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
unit = unit.ConversionFactor.UnitComponent
if unit.is_a("IfcSIUnit"):
unit_scale *= get_prefix_multiplier(unit.Prefix)
return unit_scale
+6 -1
View File
@@ -1866,7 +1866,12 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
if (entity->declaration().is(*ifcroot_type_)) { if (entity->declaration().is(*ifcroot_type_)) {
const std::string global_id = *entity->data().getArgument(0); const std::string global_id = *entity->data().getArgument(0);
byguid.erase(byguid.find(global_id)); auto it = byguid.find(global_id);
if (it != byguid.end()) {
byguid.erase(it);
} else {
Logger::Warning("GlobalId on rooted instance not encountered in map");
}
} }
byid.erase(byid.find(id)); byid.erase(byid.find(id));
+1 -2
View File
@@ -84,8 +84,7 @@ IF(PYTHONINTERP_FOUND AND NOT "${PYTHON_EXECUTABLE}" STREQUAL "")
MESSAGE(WARNING "Unable to locate Python site-package directory, unable to install the Python wrapper") MESSAGE(WARNING "Unable to locate Python site-package directory, unable to install the Python wrapper")
ELSE() ELSE()
FILE(GLOB_RECURSE sourcefiles FILE(GLOB_RECURSE sourcefiles
"${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*.py" "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*"
"${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/*.bnf"
) )
FOREACH(file ${sourcefiles}) FOREACH(file ${sourcefiles})
FILE(RELATIVE_PATH relative "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/" "${file}") FILE(RELATIVE_PATH relative "${CMAKE_CURRENT_SOURCE_DIR}/../ifcopenshell-python/ifcopenshell/" "${file}")
+119
View File
@@ -678,3 +678,122 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas
} }
%} %}
%{
PyObject* get_info_cpp(IfcUtil::IfcBaseClass* v);
// @todo refactor this to remove duplication with the typemap.
// except this is calls the above function in case of instances.
PyObject* convert_cpp_attribute_to_python(IfcUtil::ArgumentType type, Argument& arg) {
if (!arg.isNull() && type != IfcUtil::Argument_DERIVED) {
try {
switch(type) {
case IfcUtil::Argument_INT: {
int v = arg;
return pythonize(v);
break; }
case IfcUtil::Argument_BOOL: {
bool v = arg;
return pythonize(v);
break; }
case IfcUtil::Argument_DOUBLE: {
double v = arg;
return pythonize(v);
break; }
case IfcUtil::Argument_ENUMERATION:
case IfcUtil::Argument_STRING: {
std::string v = arg;
return pythonize(v);
break; }
case IfcUtil::Argument_BINARY: {
boost::dynamic_bitset<> v = arg;
return pythonize(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_INT: {
std::vector<int> v = arg;
return pythonize_vector(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_DOUBLE: {
std::vector<double> v = arg;
return pythonize_vector(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_STRING: {
std::vector<std::string> v = arg;
return pythonize_vector(v);
break; }
case IfcUtil::Argument_ENTITY_INSTANCE: {
IfcUtil::IfcBaseClass* v = arg;
return get_info_cpp(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
IfcEntityList::ptr v = arg;
auto r = PyTuple_New(v->size());
for (unsigned i = 0; i < v->size(); ++i) {
PyTuple_SetItem(r, i, get_info_cpp((*v)[i]));
}
return r;
break; }
case IfcUtil::Argument_AGGREGATE_OF_BINARY: {
std::vector< boost::dynamic_bitset<> > v = arg;
return pythonize_vector(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_INT: {
std::vector< std::vector<int> > v = arg;
return pythonize_vector2(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE: {
std::vector< std::vector<double> > v = arg;
return pythonize_vector2(v);
break; }
case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
IfcEntityListList::ptr vs = arg;
auto rs = PyTuple_New(vs->size());
for (auto it = vs->begin(); it != vs->end(); ++it) {
IfcEntityList::ptr v_i = arg;
auto r = PyTuple_New(v_i->size());
for (unsigned i = 0; i < v_i->size(); ++i) {
PyTuple_SetItem(r, i, get_info_cpp((*v_i)[i]));
}
PyTuple_SetItem(rs, std::distance(vs->begin(), it), r);
}
return rs;
break; }
case IfcUtil::Argument_EMPTY_AGGREGATE: {
return PyTuple_New(0);
break; }
}
} catch(...) {}
}
Py_INCREF(Py_None);
return Py_None;
}
%}
%inline %{
PyObject* get_info_cpp(IfcUtil::IfcBaseClass* v) {
PyObject *d = PyDict_New();
const std::vector<const IfcParse::attribute*> attrs = v->declaration().as_entity()->all_attributes();
std::vector<const IfcParse::attribute*>::const_iterator it = attrs.begin();
for (; it != attrs.end(); ++it) {
const std::string& name_cpp = (*it)->name();
auto name_py = pythonize(name_cpp);
auto attr_type = IfcUtil::from_parameter_type((*it)->type_of_attribute());
auto value_cpp = v->data().getArgument(std::distance(attrs.begin(), it));
auto value_py = convert_cpp_attribute_to_python(attr_type, *value_cpp);
PyDict_SetItem(d, name_py, value_py);
}
// @todo type and id can be static?
const std::string& type_cpp = "type";
auto type_py = pythonize(type_cpp);
const std::string& type_v_cpp = v->declaration().name();
auto type_v_py = pythonize(type_v_cpp);
PyDict_SetItem(d, type_py, type_v_py);
const std::string& id_cpp = "id";
auto id_py = pythonize(id_cpp);
auto id_v_py = pythonize(v->data().id());
PyDict_SetItem(d, id_py, id_v_py);
return d;
}
%}
-1
View File
@@ -54,7 +54,6 @@ public:
/// Applicable for OBJ, DAE, and SVG output. /// Applicable for OBJ, DAE, and SVG output.
USE_ELEMENT_STEPIDS = 1 << (IfcGeom::IteratorSettings::NUM_SETTINGS + 6), USE_ELEMENT_STEPIDS = 1 << (IfcGeom::IteratorSettings::NUM_SETTINGS + 6),
/// Use step Z UP . /// Use step Z UP .
/// Applicable for OBJ output.
USE_Z_UP = 1 << (IfcGeom::IteratorSettings::NUM_SETTINGS + 7), USE_Z_UP = 1 << (IfcGeom::IteratorSettings::NUM_SETTINGS + 7),
/// Number of different setting flags. /// Number of different setting flags.
NUM_SETTINGS = 7 NUM_SETTINGS = 7
+117 -45
View File
@@ -24,6 +24,7 @@
#include <cstdio> #include <cstdio>
#include <limits> #include <limits>
#include <algorithm> #include <algorithm>
#include <numeric>
#include <gp_Pln.hxx> #include <gp_Pln.hxx>
#include <gp_Trsf.hxx> #include <gp_Trsf.hxx>
@@ -67,6 +68,8 @@
#include <ShapeFix_Edge.hxx> #include <ShapeFix_Edge.hxx>
#include <HLRBRep_PolyHLRToShape.hxx>
#include "../ifcparse/IfcGlobalId.h" #include "../ifcparse/IfcGlobalId.h"
#include "SvgSerializer.h" #include "SvgSerializer.h"
@@ -292,16 +295,18 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
p.second.push_back(path); p.second.push_back(path);
} }
SvgSerializer::path_object& SvgSerializer::start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id) { SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, IfcUtil::IfcBaseEntity* storey, const std::string& id) {
auto key = std::make_pair(std::make_pair(storey, ""), path_object()); auto key = std::make_pair(std::make_pair(storey, ""), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second; SvgSerializer::path_object& p = paths.insert(key)->second;
drawing_metadata[key.first].pln_3d = pln;
p.first = id; p.first = id;
return p; return p;
} }
SvgSerializer::path_object& SvgSerializer::start_path(const std::string& drawing_name, const std::string& id) { SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, const std::string& drawing_name, const std::string& id) {
auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object()); auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second; SvgSerializer::path_object& p = paths.insert(key)->second;
drawing_metadata[key.first].pln_3d = pln;
p.first = id; p.first = id;
return p; return p;
} }
@@ -563,7 +568,7 @@ void SvgSerializer::write(const geometry_data& data) {
auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored; auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored;
if (use_hlr && hlr) { if (use_hlr && (hlr_poly || hlr_brep)) {
// Check if any of the bounding box points is on the correct side of the plane // Check if any of the bounding box points is on the correct side of the plane
Bnd_Box bb; Bnd_Box bb;
@@ -590,13 +595,19 @@ void SvgSerializer::write(const geometry_data& data) {
} else { } else {
state = d < 0. ? -1 : 1; state = d < 0. ? -1 : 1;
} }
if (state == 1) { if (state == -1) {
any = true; any = true;
} }
} }
if (any) { if (any) {
hlr->Add(compound_to_use); if (hlr_brep) {
hlr_brep->Add(compound_to_use);
}
if (hlr_poly) {
BRepMesh_IncrementalMesh(compound_to_use, 0.10);
hlr_poly->Load(compound_to_use);
}
} }
} }
@@ -633,6 +644,15 @@ void SvgSerializer::write(const geometry_data& data) {
cut_z = zmin + 1.; cut_z = zmin + 1.;
} }
gp_Pln pln;
if (variant.which() < 2) {
pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ());
}
else {
const auto& section = boost::get<vertical_section>(variant);
pln = section.plane;
}
gp_Vec bbmin(x1, y1, zmin); gp_Vec bbmin(x1, y1, zmin);
gp_Vec bbmax(x2, y2, zmax); gp_Vec bbmax(x2, y2, zmax);
auto bbdif = bbmax - bbmin; auto bbdif = bbmax - bbmin;
@@ -641,9 +661,9 @@ void SvgSerializer::write(const geometry_data& data) {
if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) { if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) {
if (po == nullptr) { if (po == nullptr) {
if (storey) { if (storey) {
po = &start_path(storey, data.svg_name); po = &start_path(pln, storey, data.svg_name);
} else { } else {
po = &start_path(drawing_name, data.svg_name); po = &start_path(pln, drawing_name, data.svg_name);
} }
} }
@@ -717,20 +737,12 @@ void SvgSerializer::write(const geometry_data& data) {
if (po == nullptr) { if (po == nullptr) {
if (storey) { if (storey) {
po = &start_path(storey, data.svg_name); po = &start_path(pln, storey, data.svg_name);
} else { } else {
po = &start_path(drawing_name, data.svg_name); po = &start_path(pln, drawing_name, data.svg_name);
} }
} }
// Create a horizontal cross section 1 meter above the bottom point of the shape
gp_Pln pln;
if (variant.which() < 2) {
pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ());
} else {
const auto& section = boost::get<vertical_section>(variant);
pln = section.plane;
}
TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln); TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln);
if (variant.which() == 2) { if (variant.which() == 2) {
@@ -889,7 +901,10 @@ void SvgSerializer::setBoundingRectangle(double width, double height) {
this->rescale = true; this->rescale = true;
} }
void SvgSerializer::resize() { std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
// identity matrix;
std::array<std::array<double, 3>, 3> m = {{ {{1,0,0}},{{0,1,0}},{{0,0,1}} }};
if (rescale) { if (rescale) {
// Scale the resulting image to a bounding rectangle specified by command line arguments // Scale the resulting image to a bounding rectangle specified by command line arguments
const double dx = xmax - xmin; const double dx = xmax - xmin;
@@ -918,6 +933,8 @@ void SvgSerializer::resize() {
cy = ymin * sc; cy = ymin * sc;
} }
m = {{ {{sc,0,-cx}},{{0,sc,-cy}},{{0,0,1}} }};
float_item_list::const_iterator it; float_item_list::const_iterator it;
for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) { for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) {
double& v = (*it)->value(); double& v = (*it)->value();
@@ -931,6 +948,8 @@ void SvgSerializer::resize() {
(*it)->value() *= sc; (*it)->value() *= sc;
} }
} }
return m;
} }
void SvgSerializer::resetScale() { void SvgSerializer::resetScale() {
@@ -944,7 +963,12 @@ void SvgSerializer::resetScale() {
} }
void SvgSerializer::finalize() { void SvgSerializer::finalize() {
resize(); auto m = resize();
// Update the paper space scale matrices
for (auto& p : paths) {
drawing_metadata[p.first].matrix_3 = m;
}
if (!deferred_section_data_.is_initialized() && (auto_section_ || auto_elevation_)) { if (!deferred_section_data_.is_initialized() && (auto_section_ || auto_elevation_)) {
deferred_section_data_.emplace(); deferred_section_data_.emplace();
@@ -972,28 +996,28 @@ void SvgSerializer::finalize() {
if (auto_elevation_) { if (auto_elevation_) {
{ {
gp_Pln pln(gp_Ax3( gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., -(ymin - 1.e-1), 0.), gp_Pnt(0., -(ymin - 10.), 0.),
gp_Dir(0, -1, 0), gp_Dir(0, 1, 0),
gp_Dir(-1, 0, 0))); gp_Dir(1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true }); deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true });
} }
{ {
gp_Pln pln(gp_Ax3( gp_Pln pln(gp_Ax3(
gp_Pnt(xmax + 1.e-1, (ymin + ymax) / -2., 0.), gp_Pnt(xmax + 10., 0., 0.),
gp_Dir(-1, 0, 0), gp_Dir(1, 0, 0),
gp_Dir(0, -1, 0))); gp_Dir(0, 1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true }); deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true });
} }
{ {
gp_Pln pln(gp_Ax3( gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., -(ymax + 1.e-1), 0.), gp_Pnt(0., -(ymax + 10.), 0.),
gp_Dir(0, -1, 0), gp_Dir(0, -1, 0),
gp_Dir(1, 0, 0))); gp_Dir(-1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true }); deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true });
} }
{ {
gp_Pln pln(gp_Ax3( gp_Pln pln(gp_Ax3(
gp_Pnt(xmin - 1.e-1, (ymin + ymax) / -2., 0.), gp_Pnt(xmin - 10., 0., 0.),
gp_Dir(-1, 0, 0), gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0))); gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true }); deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true });
@@ -1001,7 +1025,6 @@ void SvgSerializer::finalize() {
} }
resetScale(); resetScale();
if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) { if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) {
@@ -1018,7 +1041,11 @@ void SvgSerializer::finalize() {
} }
if (use_hlr) { if (use_hlr) {
hlr = new HLRBRep_Algo; if (use_hlr_poly_) {
hlr_poly = new HLRBRep_PolyAlgo;
} else {
hlr_brep = new HLRBRep_Algo;
}
} }
section_data_ = std::vector<section_data>{ sd }; section_data_ = std::vector<section_data>{ sd };
@@ -1028,18 +1055,30 @@ void SvgSerializer::finalize() {
if (use_hlr) { if (use_hlr) {
const auto& section = boost::get<vertical_section>(sd); const auto& section = boost::get<vertical_section>(sd);
gp_Ax2 transform = section.plane.Position().Ax2();
HLRAlgo_Projector projector(transform);
hlr->Projector(projector);
hlr->Update(); gp_Trsf trsf;
hlr->Hide(); trsf.SetTransformation(section.plane.Position());
HLRAlgo_Projector projector(trsf, false, 1.);
TopoDS_Shape hlr_compound_unmirrored;
if (use_hlr_poly_) {
hlr_poly->Projector(projector);
HLRBRep_HLRToShape hlr_shapes(hlr); hlr_poly->Update();
auto hlr_compound_unmirrored = hlr_shapes.VCompound(); HLRBRep_PolyHLRToShape hlr_shapes;
hlr_shapes.Update(hlr_poly);
hlr_compound_unmirrored = hlr_shapes.VCompound();
} else {
hlr_brep->Projector(projector);
hlr_brep->Update();
hlr_brep->Hide();
HLRBRep_HLRToShape hlr_shapes(hlr_brep);
hlr_compound_unmirrored = hlr_shapes.VCompound();
}
if (!hlr_compound_unmirrored.IsNull()) { if (!hlr_compound_unmirrored.IsNull()) {
// Compound 3D curves for mirroring to work // Compound 3D curves for mirroring to work
ShapeFix_Edge sfe; ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
@@ -1059,7 +1098,7 @@ void SvgSerializer::finalize() {
exp.Init(hlr_compound, TopAbs_EDGE); exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B; BRep_Builder B;
auto& po = start_path(drawing_name, "class=\"projection\""); auto& po = start_path(section.plane, drawing_name, "class=\"projection\"");
for (; exp.More(); exp.Next()) { for (; exp.More(); exp.Next()) {
TopoDS_Wire w; TopoDS_Wire w;
B.MakeWire(w); B.MakeWire(w);
@@ -1070,12 +1109,15 @@ void SvgSerializer::finalize() {
} }
} }
resize(); auto m3 = resize();
auto k = std::make_pair(nullptr, drawing_name);
drawing_metadata[k].matrix_3 = m3;
resetScale(); resetScale();
if (use_hlr) { if (hlr_brep) hlr_brep.Nullify();
hlr.Nullify(); if (hlr_poly) hlr_poly.Nullify();
}
} }
} }
@@ -1089,11 +1131,11 @@ void SvgSerializer::finalize() {
} }
std::ostringstream oss; std::ostringstream oss;
if (it->first.first) { if (it->first.first) {
svg_file << " <g " << nameElement(it->first.first) << ">\n"; svg_file << " <g " << nameElement(it->first.first) << " " << writeMetadata(drawing_metadata[it->first]) << ">\n";
} else { } else {
auto n = it->first.second; auto n = it->first.second;
IfcUtil::escape_xml(n); IfcUtil::escape_xml(n);
svg_file << " <g " << namespace_prefix_ << "name=\"" << n << "\" class=\"section\">\n"; svg_file << " <g " << namespace_prefix_ << "name=\"" << n << "\" class=\"section\" " << writeMetadata(drawing_metadata[it->first]) << ">\n";
} }
} }
svg_file << " <g " << it->second.first << ">\n"; svg_file << " <g " << it->second.first << ">\n";
@@ -1285,3 +1327,33 @@ void SvgSerializer::setSectionHeightsFromStoreys(double offset) {
section_data_->push_back(horizontal_plan_at_element{}); section_data_->push_back(horizontal_plan_at_element{});
} }
} }
namespace {
std::string array_to_string(double v) {
return std::to_string(v);
}
template <typename T>
std::string array_to_string(const T& v) {
return "[" + std::accumulate(
v.begin() + 1, v.end(),
array_to_string(v.front()),
[](const std::string& accum, decltype(*v.cbegin())& item) {
return accum + "," + array_to_string(item);
}) + "]";
}
}
std::string SvgSerializer::writeMetadata(const drawing_meta& m) {
gp_Trsf trsf;
trsf.SetTransformation(m.pln_3d.Position(), gp::XOY());
auto m43 = IfcGeom::Matrix<real_t>(IfcGeom::ElementSettings(IfcGeom::IteratorSettings(), 1., ""), trsf).data();
std::array<std::array<double, 4>, 4> m4 = {{
{{ (double)m43[0], (double)m43[3], (double)m43[6], (double)m43[9] }},
{{ (double)m43[1], (double)m43[4], (double)m43[7], (double)m43[10] }},
{{ (double)m43[2], (double)m43[5], (double)m43[8], (double)m43[11] }},
{{ 0, 0, 0, 1 }}
}};
return namespace_prefix_ + "plane=\""+ array_to_string(m4) +"\" " +
namespace_prefix_ + "matrix3=\"" + array_to_string(m.matrix_3) + "\"";
}
+22 -5
View File
@@ -29,6 +29,8 @@
#include <HLRBRep_Algo.hxx> #include <HLRBRep_Algo.hxx>
#include <HLRBRep_HLRToShape.hxx> #include <HLRBRep_HLRToShape.hxx>
#include <HLRBRep_PolyAlgo.hxx>
#include <HLRAlgo_Projector.hxx>
#include <gp_Pln.hxx> #include <gp_Pln.hxx>
#include <sstream> #include <sstream>
@@ -107,6 +109,11 @@ struct geometry_data {
std::string ifc_name, svg_name; std::string ifc_name, svg_name;
}; };
struct drawing_meta {
gp_Pln pln_3d;
std::array<std::array<double, 3>, 3> matrix_3;
};
class SvgSerializer : public GeometrySerializer { class SvgSerializer : public GeometrySerializer {
public: public:
typedef std::pair<std::string, std::vector<util::string_buffer> > path_object; typedef std::pair<std::string, std::vector<util::string_buffer> > path_object;
@@ -121,11 +128,12 @@ protected:
bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_; bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_;
bool draw_door_arcs_, is_floor_plan_; bool draw_door_arcs_, is_floor_plan_;
bool auto_section_, auto_elevation_; bool auto_section_, auto_elevation_;
bool use_namespace_; bool use_namespace_, use_hlr_poly_;
IfcParse::IfcFile* file; IfcParse::IfcFile* file;
IfcUtil::IfcBaseEntity* storey_; IfcUtil::IfcBaseEntity* storey_;
std::multimap<drawing_key, path_object, storey_sorter> paths; std::multimap<drawing_key, path_object, storey_sorter> paths;
std::map<drawing_key, drawing_meta> drawing_metadata;
float_item_list xcoords, ycoords, radii; float_item_list xcoords, ycoords, radii;
size_t xcoords_begin, ycoords_begin, radii_begin; size_t xcoords_begin, ycoords_begin, radii_begin;
@@ -134,7 +142,8 @@ protected:
std::list<geometry_data> element_buffer_; std::list<geometry_data> element_buffer_;
Handle(HLRBRep_Algo) hlr; Handle(HLRBRep_Algo) hlr_brep;
Handle(HLRBRep_PolyAlgo) hlr_poly;
std::string namespace_prefix_; std::string namespace_prefix_;
public: public:
@@ -154,6 +163,7 @@ public:
, auto_section_(false) , auto_section_(false)
, auto_elevation_(false) , auto_elevation_(false)
, use_namespace_(false) , use_namespace_(false)
, use_hlr_poly_(false)
, file(0) , file(0)
, storey_(0) , storey_(0)
, xcoords_begin(0) , xcoords_begin(0)
@@ -171,8 +181,8 @@ public:
void write(const IfcGeom::BRepElement<real_t>* o); void write(const IfcGeom::BRepElement<real_t>* o);
void write(path_object& p, const TopoDS_Wire& wire); void write(path_object& p, const TopoDS_Wire& wire);
void write(const geometry_data& data); void write(const geometry_data& data);
path_object& start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id); path_object& start_path(const gp_Pln& p, IfcUtil::IfcBaseEntity* storey, const std::string& id);
path_object& start_path(const std::string& drawing_name, const std::string& id); path_object& start_path(const gp_Pln& p, const std::string& drawing_name, const std::string& id);
bool isTesselated() const { return false; } bool isTesselated() const { return false; }
void finalize(); void finalize();
void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {} void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {}
@@ -184,7 +194,7 @@ public:
void setPrintSpaceAreas(bool b) { print_space_areas_ = b; } void setPrintSpaceAreas(bool b) { print_space_areas_ = b; }
void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; } void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; }
void resize(); std::array<std::array<double, 3>, 3> resize();
void resetScale(); void resetScale();
void setSectionRef(const boost::optional<std::string>& s) { void setSectionRef(const boost::optional<std::string>& s) {
@@ -207,6 +217,10 @@ public:
namespace_prefix_ = use_namespace_ ? "ifc:" : "data-"; namespace_prefix_ = use_namespace_ ? "ifc:" : "data-";
} }
void setUseHlrPoly(bool b) {
use_hlr_poly_ = b;
}
void setScale(double s) { scale_ = s; } void setScale(double s) { scale_ = s; }
void setDrawingCenter(double x, double y) { void setDrawingCenter(double x, double y) {
center_x_ = x; center_y_ = y; center_x_ = x; center_y_ = y;
@@ -221,6 +235,9 @@ public:
return GeometrySerializer::object_id(o); return GeometrySerializer::object_id(o);
} }
} }
protected:
std::string writeMetadata(const drawing_meta& m);
}; };
#endif #endif