From eb0f2de197c94e18d6eed83e56351e1c189e07ed Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 21 Dec 2020 18:26:39 +1100 Subject: [PATCH 01/82] New snap spaces together utility for cleaning up spaces for energy modeling --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 46 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 2 + 3 files changed, 49 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index d2b38f8258..8cdb13edd9 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -238,6 +238,7 @@ if bpy is not None: operator.ExecuteBlenderClash, operator.CleanWireframes, operator.LinkIfc, + operator.SnapSpacesTogether, prop.StrProperty, prop.Attribute, prop.MaterialLayer, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f6474d0d68..15aaee35b6 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -5010,3 +5010,49 @@ class LinkIfc(bpy.types.Operator): def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} + + +class SnapSpacesTogether(bpy.types.Operator): + bl_idname = "bim.snap_spaces_together" + bl_label = "Snap Spaces Together" + + def execute(self, context): + threshold = 0.5 + processed_polygons = set() + for obj in bpy.context.selected_objects: + if obj.type != "MESH": + continue + for polygon in obj.data.polygons: + center = obj.matrix_world @ polygon.center + distance = None + for obj2 in bpy.context.selected_objects: + if obj2 == obj or obj.type != "MESH": + continue + result = obj2.ray_cast( + obj2.matrix_world.inverted() @ center, + polygon.normal, + distance=threshold + ) + if not result[0]: + continue + hit = obj2.matrix_world @ result[1] + distance = (hit - center).length / 2 + if distance < 0.01: + distance = None + break + + if (obj2.name, result[3]) in processed_polygons: + distance *= 2 + continue + + offset = polygon.normal * distance * -1 + processed_polygons.add((obj2.name, result[3])) + for v in obj2.data.polygons[result[3]].vertices: + obj2.data.vertices[v].co += offset + break + if distance: + offset = polygon.normal * distance + processed_polygons.add((obj.name, polygon.index)) + for v in polygon.vertices: + obj.data.vertices[v].co += offset + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 91b23aa6cd..01eb0d76a3 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2525,6 +2525,8 @@ class BIM_PT_misc_utilities(Panel): row.operator("bim.set_override_colour") row = layout.row(align=True) row.operator("bim.set_viewport_shadow_from_sun") + row = layout.row(align=True) + row.operator("bim.snap_spaces_together") class BIM_PT_debug(Panel): From 1074c2431cf761f23a495d2dc05ece78d8cad9d3 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 10:19:37 +0100 Subject: [PATCH 02/82] bimtester: add dummy feature file to be able to run a command to get all implemented steps --- src/ifcbimtester/bimtester/features/dummy.feature | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/ifcbimtester/bimtester/features/dummy.feature diff --git a/src/ifcbimtester/bimtester/features/dummy.feature b/src/ifcbimtester/bimtester/features/dummy.feature new file mode 100644 index 0000000000..59da694397 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/dummy.feature @@ -0,0 +1,3 @@ + # dummy file to be able to run the command to get a list of all steps + # the command has to be run in the parent directory of this one + # behave --steps-catalog From 21a7599d45357f292f641e4435de69945148990f Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 10:28:03 +0100 Subject: [PATCH 03/82] bimtester: small code improvements --- src/ifcbimtester/bimtester/features/environment.py | 4 ++++ src/ifcbimtester/bimtester/features/steps/ifcdata.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ifcbimtester/bimtester/features/environment.py b/src/ifcbimtester/bimtester/features/environment.py index 9bce6c6fb4..5e5ef6303e 100644 --- a/src/ifcbimtester/bimtester/features/environment.py +++ b/src/ifcbimtester/bimtester/features/environment.py @@ -3,6 +3,10 @@ from behave.model import Scenario def before_all(context): userdata = context.config.userdata + context.ifcbasename = userdata["ifcbasename"] context.localedir = userdata.get("localedir") + + # do not break after a failed scenario + # https://community.osarch.org/discussion/comment/3328/#Comment_3328 continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True) Scenario.continue_after_failed_step = continue_after_failed diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata.py b/src/ifcbimtester/bimtester/features/steps/ifcdata.py index 99096f0e6c..af7bba824c 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata.py @@ -1,6 +1,6 @@ -import gettext -import os -from behave import step, given +import gettext # noqa +from behave import given +from behave import step from ifcdata_methods import assert_schema from utils import IfcFile From 81326ba48f429edf5bc7c7c5a9737af7b110d6b1 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 10:31:05 +0100 Subject: [PATCH 04/82] bimtester: improve and translate geometric representation class type test --- .../features/steps/geometric_detail.py | 9 +++ .../features/steps/geometric_detail_de.py | 10 +++ .../steps/geometric_detail_methods.py | 54 ++++++++++++++++ .../bimtester/features/steps/steps.py | 29 --------- .../bimtester/features/steps/utils.py | 62 ++++++++++++++++++- 5 files changed, 133 insertions(+), 31 deletions(-) create mode 100644 src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py create mode 100644 src/ifcbimtester/bimtester/features/steps/geometric_detail_methods.py diff --git a/src/ifcbimtester/bimtester/features/steps/geometric_detail.py b/src/ifcbimtester/bimtester/features/steps/geometric_detail.py index 930e511d0e..594b42b4e6 100644 --- a/src/ifcbimtester/bimtester/features/steps/geometric_detail.py +++ b/src/ifcbimtester/bimtester/features/steps/geometric_detail.py @@ -1,6 +1,9 @@ from behave import step +from geometric_detail_methods import class_geometric_representation +from utils import assert_elements from utils import IfcFile +from utils import switch_locale @step("All elements must be under {number} polygons") @@ -29,3 +32,9 @@ def step_impl(context, number): for error in errors: message += "Polygons: {} - {}\n".format(error[0], error[1]) assert False, message + + +@step("all {ifc_class} elements have an {representation_class} representation") +def step_impl(context, ifc_class, representation_class): + switch_locale(context.localedir, "en") + class_geometric_representation(context, ifc_class, representation_class) diff --git a/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py b/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py new file mode 100644 index 0000000000..bb574db3f5 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py @@ -0,0 +1,10 @@ +from behave import step + +from geometric_detail_methods import class_geometric_representation +from utils import switch_locale + + +step("Alle {ifc_class} Bauteile müssen eine geometrische Repräsentation der Klasse {representation_class} verwenden") +def step_impl(context, ifc_class, representation_class): + switch_locale(context.localedir, "de") + class_geometric_representation(context, ifc_class, representation_class) diff --git a/src/ifcbimtester/bimtester/features/steps/geometric_detail_methods.py b/src/ifcbimtester/bimtester/features/steps/geometric_detail_methods.py new file mode 100644 index 0000000000..5a321abc32 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/geometric_detail_methods.py @@ -0,0 +1,54 @@ +import gettext # noqa +from utils import assert_elements +from utils import IfcFile + + +def class_geometric_representation(context, ifc_class, representation_class): + + def is_item_a_representation(item, representation): + if "/" in representation: + for cls in representation.split("/"): + if item.is_a(cls): + return True + elif item.is_a(representation): + return True + + context.falseelems = [] + context.falseguids = [] + context.falseprops = {} + rep = None + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + if not elem.Representation: + continue + has_representation = False + for representation in elem.Representation.Representations: + for item in representation.Items: + if item.is_a("IfcMappedItem"): + # We only check one more level deep. + for item2 in item.MappingSource.MappedRepresentation.Items: + if is_item_a_representation(item2, representation_class): + has_representation = True + rep = item2 + else: + if is_item_a_representation(item, representation_class): + has_representation = True + rep = item + if not has_representation: + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + context.falseprops[elem.id()] = str(rep) + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + assert_elements( + ifc_class, + context.elemcount, + context.falsecount, + context.falseelems, + message_all_falseelems=_("All {elemcount} {ifc_class} elements are not a {parameter} representation."), + message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are not a {parameter} representation: {falseelems}"), + message_no_elems=_("There are no {ifc_class} elements in the IFC file."), + parameter=representation_class + ) diff --git a/src/ifcbimtester/bimtester/features/steps/steps.py b/src/ifcbimtester/bimtester/features/steps/steps.py index 8627efd419..9990a9ef97 100644 --- a/src/ifcbimtester/bimtester/features/steps/steps.py +++ b/src/ifcbimtester/bimtester/features/steps/steps.py @@ -18,35 +18,6 @@ def step_impl(context, ifc_class, pattern): assert False -@step("all {ifc_class} elements have an {representation_class} representation") -def step_impl(context, ifc_class, representation_class): - def is_item_a_representation(item, representation): - if "/" in representation: - for cls in representation.split("/"): - if item.is_a(cls): - return True - elif item.is_a(representation): - return True - - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - if not element.Representation: - continue - has_representation = False - for representation in element.Representation.Representations: - for item in representation.Items: - if item.is_a("IfcMappedItem"): - # We only check one more level deep. - for item2 in item.MappingSource.MappedRepresentation.Items: - if is_item_a_representation(item2, representation_class): - has_representation = True - else: - if is_item_a_representation(item, representation_class): - has_representation = True - if not has_representation: - assert False - - use_step_matcher("re") diff --git a/src/ifcbimtester/bimtester/features/steps/utils.py b/src/ifcbimtester/bimtester/features/steps/utils.py index 30a79c5b60..f3078f8e61 100644 --- a/src/ifcbimtester/bimtester/features/steps/utils.py +++ b/src/ifcbimtester/bimtester/features/steps/utils.py @@ -1,3 +1,4 @@ +import gettext import ifcopenshell import ifcopenshell.express import ifcopenshell.util @@ -99,9 +100,66 @@ def assert_pset(element, pset_name, prop_name=None, value=None): ) +def assert_elements( + ifc_class, + elemcount, + falsecount, + falseelems, + message_all_falseelems, + message_some_falseelems, + message_no_elems, + parameter=None +): + if elemcount > 0 and falsecount == 0: + return # Test OK + elif elemcount == 0: + assert False, ( + message_no_elems.format( + ifc_class=ifc_class + ) + ) + elif falsecount == elemcount: + if parameter is None: + assert False, ( + message_all_falseelems.format( + elemcount=elemcount, + ifc_class=ifc_class + ) + ) + else: + assert False, ( + message_all_falseelems.format( + elemcount=elemcount, + ifc_class=ifc_class, + parameter=parameter + ) + ) + elif falsecount > 0 and falsecount < elemcount: + if parameter is None: + assert False, ( + message_some_falseelems.format( + falsecount=falsecount, + elemcount=elemcount, + ifc_class=ifc_class, + falseelems=falseelems, + ) + ) + else: + assert False, ( + message_some_falseelems.format( + falsecount=falsecount, + elemcount=elemcount, + ifc_class=ifc_class, + falseelems=falseelems, + parameter=parameter + ) + ) + else: + assert False, _("Error in falsecount, something went wrong.") + + def switch_locale(locale_dir, locale_id="en"): - from gettext import translation - newlang = translation( + newlang = gettext.translation( "messages", localedir=locale_dir, languages=[locale_id] From b8fe04b7a94e82b6178191905adeee0b94e28a5d Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 11:28:51 +0100 Subject: [PATCH 05/82] bimtester: fix error in step code --- .../bimtester/features/steps/geometric_detail_de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py b/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py index bb574db3f5..8b1977b42e 100644 --- a/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py +++ b/src/ifcbimtester/bimtester/features/steps/geometric_detail_de.py @@ -4,7 +4,7 @@ from geometric_detail_methods import class_geometric_representation from utils import switch_locale -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): switch_locale(context.localedir, "de") class_geometric_representation(context, ifc_class, representation_class) From 3b856f446e81922968d3f1e2c531f6797c9f46fe Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 12:08:20 +0100 Subject: [PATCH 06/82] bimtester: remove binary mo files from repository --- src/ifcbimtester/README.md | 11 +++++++++++ .../bimtester/locale/de/LC_MESSAGES/messages.mo | Bin 556 -> 0 bytes .../bimtester/locale/en/LC_MESSAGES/messages.mo | Bin 532 -> 0 bytes .../bimtester/locale/fr/LC_MESSAGES/messages.mo | Bin 415 -> 0 bytes 4 files changed, 11 insertions(+) delete mode 100644 src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.mo delete mode 100644 src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.mo delete mode 100644 src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.mo diff --git a/src/ifcbimtester/README.md b/src/ifcbimtester/README.md index a02d4bf267..162b355806 100644 --- a/src/ifcbimtester/README.md +++ b/src/ifcbimtester/README.md @@ -16,3 +16,14 @@ python3 ./bimtester.py -g `$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features:features" bimtester.py` + Windows: `$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features;features" bimtester.py` + + +### Translation files ++ the binary mo translation files are not part of the repo ++ they might be needed to be recreated ++ use the following commands on Linux +``` +cd YourIfcopenshellRepository/src/ifcbimtester/bimtester/locale +pybabel compile -d . + +``` diff --git a/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.mo b/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.mo deleted file mode 100644 index f95ef3bb99b1f08629f04234d170cfa1b397428e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 556 zcmZ9J&2HN;41m32yZG315A1Ejs?_$O2%Ki#@%38z@SZWxtn+#&ZI0j) z0k3OZ5Gs)17b{##uuHhRhj~MwjVF`}N=s}Ca%X>YtQabBfzE4dQpk(RX?8q=i>#Oq zCud=SwRI%MzSK$_HKi9bn?g1|9F7hS2ZJJ;PQ!d63cS*70x^(;DI`%6i8v8)7oxp1 zj=x876on({Nz9xyzLI2J3T!cDBTK%a@c%)~P_A1G?2kqVmF`$q{s{A`ak3I8)~$UC zrhYm4e$sL7nXKx+FohUrEhR|^Fo0@A8Uej(}No{(tSV`xR`VB2b z_bsk{8=(t1viklRUri+i=>AAYNISqz2TCKfzh}TSbo((n_zQK0pW*-j diff --git a/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.mo b/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.mo deleted file mode 100644 index a8c96e94499ccde60baae75ba5f691d5c70540cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 532 zcmb7>y-ve05P$=MB_lJ#v4OxP$xu}(Mg0q?sHGvTfYoiTsZnZ2wu4$B#8dEkj64Sy zKp;j={-paqpZ&JC^Xian6Z^you}hp0mBa)iZivN}Q@D+i$zXi#CRiwK z*~nxfW!*9}Y^p+tMxDX1-Rbv}Xfko*F-!1KR1H|4TMQxa0+0Ct^BdsRLq9n7e9v=- z+*mf%T$-F)r9&W(%u1e71pJ)$NyfGGw5diYt6Rfs@~F+u zGOU{4(hlhW|80j?3XgDbJwR!kM#n&b=cAccPAKr+H(=$&r@L!vF4X{U-ng;C4O_P* z;c>2e<5n0Fsf{kBsu&K}(xE4zgn@wY6MKfId@X8By*@a+l7Fp>m1$8*2MYB9)cXMo C+H}zX From 26d537cb4f715166d407ecaba8b810b1289c2201 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 12:12:43 +0100 Subject: [PATCH 07/82] gitignore: add gettext binary translation files, used in bimtester --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 902ea7f53b..b4ee87ef66 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ __pycache__ .vscode # PyCharm files .idea +# gettext binary translation files +*.mo From 97913e85927786ae9e40e0bc70510a1d16f72d85 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 08/82] fixed #1157 again with proper perspective correction --- .../blenderbim/bim/decoration.py | 85 +++++++++---------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 3bfedc405f..22317ec3de 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -4,7 +4,7 @@ from functools import reduce from itertools import chain from bpy.types import SpaceView3D -from mathutils import Vector +from mathutils import Vector, Matrix import bpy import blf from bpy_extras.view3d_utils import location_3d_to_region_2d @@ -51,6 +51,8 @@ class DimensionDecorator(ViewDecorator): layout(lines) in; layout(line_strip, max_vertices=10) out; + uniform float windowW; + uniform float windowH; uniform float angle; uniform float length; uniform float aspect; @@ -58,37 +60,38 @@ class DimensionDecorator(ViewDecorator): void main() { /** generates arrows from lines */ + // converting NDC to/from window coords + float hw = windowW/2, hh= windowH/2; + mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + + vec4 p0w = windowMatrix * (p0 / p0.w), p1w = windowMatrix * (p1 / p1.w); + + // arrow head of specified length in direction of a segment + // rotating the head left/right to form triangle + vec4 head = normalize(p1w - p0w) * length; float c = cos(angle), s = sin(angle); - mat4 rot_a = mat4( c, -s, 0, 0, - +s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mat4 rot_b = mat4( c, +s, 0, 0, - -s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); + vec4 head_a = mat4( c, -s, 0, 0, +s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * head; + vec4 head_b = mat4( c, +s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * head; - // converting to and from square-space coordinates to calculate arrows - mat4 clip2square = mat4(aspect, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - mat4 square2clip = mat4(1/aspect, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + // projecting back to NDC + vec4 head_ndc = unwindowMatrix * head; + vec4 head_a_ndc = unwindowMatrix * head_a; + vec4 head_b_ndc = unwindowMatrix * head_b; - vec4 dir = normalize((p1 - p0) * clip2square) * length; - vec4 head = dir * square2clip; - vec4 arr_a = dir * rot_a * square2clip; - vec4 arr_b = dir * rot_b * square2clip; - - gl_Position = p0 + head; + gl_Position = p0 + head_ndc * p0.w; EmitVertex(); - gl_Position = p1 - head; + gl_Position = p1 - head_ndc * p1.w; EmitVertex(); EndPrimitive(); gl_Position = p0; EmitVertex(); - gl_Position = p0 + arr_a; + gl_Position = p0 + head_a_ndc * p0.w; EmitVertex(); - gl_Position = p0 + arr_b; + gl_Position = p0 + head_b_ndc * p0.w; EmitVertex(); gl_Position = p0; EmitVertex(); @@ -96,20 +99,13 @@ class DimensionDecorator(ViewDecorator): gl_Position = p1; EmitVertex(); - gl_Position = p1 - arr_b; + gl_Position = p1 - head_b_ndc * p1.w; EmitVertex(); - gl_Position = p1 - arr_a; + gl_Position = p1 - head_a_ndc * p1.w; EmitVertex(); gl_Position = p1; EmitVertex(); EndPrimitive(); -/* - gl_Position = vec4(0, 0, 0, 1) * square2clip; - EmitVertex(); - gl_Position = vec4(0.25, 0.25, 0, 1) * square2clip; - EmitVertex(); - EndPrimitive(); -*/ } """ FRAG_GLSL = """ @@ -145,11 +141,11 @@ class DimensionDecorator(ViewDecorator): for segm in segments: self.draw_label(segm, f"{segm[2]:.2f}") - segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Equal")) + # segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Equal")) - self.draw_arrows(segments) - for segm in segments: - self.draw_label(segm, "EQ") + # self.draw_arrows(segments) + # for segm in segments: + # self.draw_label(segm, "EQ") def get_curves(self, collection, basename): return list(filter(lambda o: basename in o.name, collection.objects)) @@ -222,20 +218,15 @@ class DimensionDecorator(ViewDecorator): batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) self.shader.bind() - region = self.context.region_data - matrix = region.perspective_matrix - aspect = self.context.region.width / self.context.region.height - self.shader.uniform_float("viewMatrix", matrix) + region = self.context.region + region3d = self.context.region_data + + self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) + self.shader.uniform_float("windowW", region.width) + self.shader.uniform_float("windowH", region.height) + # TODO: get everything from styles - self.shader.uniform_float('aspect', aspect) self.shader.uniform_float('color', (1.0, 1.0, 1.0)) self.shader.uniform_float('angle', math.pi / 12) - - # brute-force perspective fix - # TODO: move the fix into shader + align heads to view plane - length = 32 / self.context.region.height - if region.is_perspective: - length *= 8 - - self.shader.uniform_float('length', length) + self.shader.uniform_float('length', 16) batch.draw(self.shader) From 0c0663963562b0581e3756d99233a0fe71964a62 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 09/82] decorator classes for Dim/Eq/Leader/Stair/Hidden --- .../blenderbim/bim/decoration.py | 374 ++++++++++++------ src/ifcblenderexport/blenderbim/bim/prop.py | 8 + 2 files changed, 256 insertions(+), 126 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 22317ec3de..5bccfd7a60 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -14,7 +14,45 @@ from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertForma from gpu_extras.batch import batch_for_shader -class ViewDecorator(object): +class Shaders: + pass + + +class BaseDecorator(): + # base name of objects to decorate + basename = "IfcAnnotation/Something" + + VERT_GLSL = """ + uniform mat4 viewMatrix; + in vec3 pos; + out vec4 gl_Position; + + void main() { + gl_Position = viewMatrix * vec4(pos, 1.0); + } + """ + GEOM_GLSL = """ + layout(lines) in; + layout(line_strip, max_vertices=2) out; + + void main() { + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + gl_Position = p0; + EmitVertex(); + gl_Position = p1; + EmitVertex(); + EndPrimitive(); + } + """ + FRAG_GLSL = """ + uniform vec3 color; + out vec4 fragColor; + + void main() { + fragColor = vec4(color, 1.0); + } + """ + # class var for single handler installed = None @@ -31,97 +69,9 @@ class ViewDecorator(object): pass cls.installed = None - -class DimensionDecorator(ViewDecorator): - """Decorates dimension curves - - outlines each segment with an arrow - - puts metric text next to each segment - """ - - VERT_GLSL = """ - uniform mat4 viewMatrix; - in vec3 pos; - out vec4 gl_Position; - - void main() { - gl_Position = viewMatrix * vec4(pos, 1.0); - } - """ - GEOM_GLSL = """ - layout(lines) in; - layout(line_strip, max_vertices=10) out; - - uniform float windowW; - uniform float windowH; - uniform float angle; - uniform float length; - uniform float aspect; - - void main() { - /** generates arrows from lines */ - - // converting NDC to/from window coords - float hw = windowW/2, hh= windowH/2; - mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - mat4 unwindowMatrix = inverse(windowMatrix); - - vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - - vec4 p0w = windowMatrix * (p0 / p0.w), p1w = windowMatrix * (p1 / p1.w); - - // arrow head of specified length in direction of a segment - // rotating the head left/right to form triangle - vec4 head = normalize(p1w - p0w) * length; - float c = cos(angle), s = sin(angle); - vec4 head_a = mat4( c, -s, 0, 0, +s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * head; - vec4 head_b = mat4( c, +s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * head; - - // projecting back to NDC - vec4 head_ndc = unwindowMatrix * head; - vec4 head_a_ndc = unwindowMatrix * head_a; - vec4 head_b_ndc = unwindowMatrix * head_b; - - gl_Position = p0 + head_ndc * p0.w; - EmitVertex(); - gl_Position = p1 - head_ndc * p1.w; - EmitVertex(); - EndPrimitive(); - - gl_Position = p0; - EmitVertex(); - gl_Position = p0 + head_a_ndc * p0.w; - EmitVertex(); - gl_Position = p0 + head_b_ndc * p0.w; - EmitVertex(); - gl_Position = p0; - EmitVertex(); - EndPrimitive(); - - gl_Position = p1; - EmitVertex(); - gl_Position = p1 - head_b_ndc * p1.w; - EmitVertex(); - gl_Position = p1 - head_a_ndc * p1.w; - EmitVertex(); - gl_Position = p1; - EmitVertex(); - EndPrimitive(); - } - """ - FRAG_GLSL = """ - uniform vec3 color; - out vec4 fragColor; - - void main() { - fragColor = vec4(color, 1.0); - } - """ - def __init__(self, props, context): self.context = context self.props = props - self.font_id = 0 # TODO: take font from styles - self.dpi = context.preferences.system.dpi self.shader = self.create_shader() @classmethod @@ -132,45 +82,149 @@ class DimensionDecorator(ViewDecorator): # get active drawing, if any if self.props.active_drawing_index is None or len(self.props.drawings) == 0: return + + objects = list(self.get_objects()) + if not objects: + return + + self.decorate(objects) + + def get_objects(self): + """find relevant objects + using class.basename + + returns: iterable of blender objects + """ drawing = self.props.drawings[self.props.active_drawing_index] collection = bpy.data.collections.get("IfcGroup/" + drawing.name) + return filter(lambda o: self.basename in o.name, collection.objects) - segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Dimension")) + def decorate(self, objects): + """perform actuall drawing stuff""" + raise NotImplementedError() - self.draw_arrows(segments) - for segm in segments: - self.draw_label(segm, f"{segm[2]:.2f}") - - # segments = self.get_segments(self.get_curves(collection, "IfcAnnotation/Equal")) - - # self.draw_arrows(segments) - # for segm in segments: - # self.draw_label(segm, "EQ") - - def get_curves(self, collection, basename): - return list(filter(lambda o: basename in o.name, collection.objects)) - - def get_segments(self, curves): - return list(chain.from_iterable(self.iter_segments(curve) for curve in curves)) - - def iter_segments(self, curve): - """Yields each segment converted to world coords - (v0, v1, length) + def flat_points(self, segments): + """flatten segments into list of their points + returns: iterable of points as coords tuples """ - for spline in curve.data.splines: + def coords(v): + return tuple(v)[:3] + return reduce(lambda points, segm: points + [coords(segm[0]), coords(segm[1])], segments, []) + + +class PathDecorator: + def get_segments(self, object): + """extract segments from curve object""" + for spline in object.data.splines: spline_points = spline.bezier_points if spline.bezier_points else spline.points - points = [curve.matrix_world @ p.co for p in spline_points] + points = [object.matrix_world @ p.co for p in spline_points] for i in range(len(points)-1): p0 = points[i] p1 = points[i+1] - length = (p1 - p0).length - yield (p0, p1, length) + yield (p0, p1) + + +class MeshDecorator: + def get_segments(self, object): + """extract segments from mesh object""" + for edge in object.data.edges: + p0 = object.data.vertices[edge.vertices[0]].co + p1 = object.data.vertices[edge.vertices[1]].co + yield (p0, p1) + + +class DimensionDecorator(PathDecorator, BaseDecorator): + """Decorator for dimension objects + - outlines each segment + - augments with arrows on both sides + - puts metric text next to each segment + """ + basename = "IfcAnnotation/Dimension" + installed = None + + GEOM_GLSL = """ + layout(lines) in; + layout(line_strip, max_vertices=10) out; + + uniform float windowW; + uniform float windowH; + uniform float arrow_length; + uniform float arrow_angle; + + void arrow_head(in vec2 p0, in vec2 p1, out vec2 head[3]) { + vec2 nose = normalize(p1 - p0) * arrow_length; + float c = cos(arrow_angle), s = sin(arrow_angle); + head[0] = nose; + head[1] = mat2( c, -s, +s, c) * nose; + head[2] = mat2( c, +s, -s, c) * nose; + } + + void main() { + /** generates arrows from lines */ + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + + // converting NDC to/from window coords + float hw = windowW/2, hh= windowH/2; + mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + + vec2 head[3]; + vec4 head_ndc[3]; + + vec4 p0w = windowMatrix * (p0 / p0.w), p1w = windowMatrix * (p1 / p1.w); + arrow_head(p0w.xy, p1w.xy, head); + for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0); + + gl_Position = p0 + head_ndc[0] * p0.w; + EmitVertex(); + gl_Position = p1 - head_ndc[0] * p1.w; + EmitVertex(); + EndPrimitive(); + + gl_Position = p0; + EmitVertex(); + gl_Position = p0 + head_ndc[1] * p0.w; + EmitVertex(); + gl_Position = p0 + head_ndc[2] * p0.w; + EmitVertex(); + gl_Position = p0; + EmitVertex(); + EndPrimitive(); + + gl_Position = p1; + EmitVertex(); + gl_Position = p1 - head_ndc[1] * p1.w; + EmitVertex(); + gl_Position = p1 - head_ndc[2] * p1.w; + EmitVertex(); + gl_Position = p1; + EmitVertex(); + EndPrimitive(); + } + """ + + def __init__(self, props, context): + super().__init__(props, context) + self.font_id = 0 + self.dpi = context.preferences.system.dpi + + def decorate(self, objects): + segments = list(chain.from_iterable(self.get_segments(obj) for obj in objects)) + if not segments: + return + + for segm in segments: + length = (segm[1] - segm[0]).length + text = f"{length:.2f}" + self.draw_label(segm, text) + + self.draw_arrows(segments) def draw_label(self, segm, text): """Draw text of segment length aligned and centered at segment middle """ - p0, p1, _ = segm + p0, p1 = segm # convert to view coords region = self.context.region @@ -210,23 +264,91 @@ class DimensionDecorator(ViewDecorator): blf.disable(self.font_id, blf.ROTATION) def draw_arrows(self, segments): - def coords(segm): - return [(segm[0].x, segm[0].y, segm[0].z), - (segm[1].x, segm[1].y, segm[1].z)] - points = list(reduce(lambda points, segm: points + coords(segm), - segments, [])) - batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) - self.shader.bind() - region = self.context.region region3d = self.context.region_data + points = self.flat_points(segments) + batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) + self.shader.bind() self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) self.shader.uniform_float("windowW", region.width) self.shader.uniform_float("windowH", region.height) - - # TODO: get everything from styles + # # TODO: get everything from styles + self.shader.uniform_float('color', (1.0, 1.0, 1.0)) + self.shader.uniform_float('arrow_angle', math.pi / 12) + self.shader.uniform_float('arrow_length', 16) + batch.draw(self.shader) + + +class EqualityDecorator(DimensionDecorator): + """Decorator for equality objects + - outlines each segment + - augments with arrows on both sides + - puts 'EQ' label + """ + basename = "IfcAnnotation/Equal" + installed = None + + def decorate(self, objects): + segments = list(chain.from_iterable(self.get_segments(obj) for obj in objects)) + if not segments: + return + + for segm in segments: + self.draw_label(segm, 'EQ') + + self.draw_arrows(segments) + + +class LeaderDecorator(PathDecorator, BaseDecorator): + basename = "IfcAnnotation/Leader" + installed = None + + def decorate(self, objects): + segments = list(chain.from_iterable(self.get_segments(obj) for obj in objects)) + if not segments: + return + + region3d = self.context.region_data + points = self.flat_points(segments) + batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) + self.shader.bind() + self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) + self.shader.uniform_float('color', (1.0, 1.0, 1.0)) + batch.draw(self.shader) + + +class StairDecorator(PathDecorator, BaseDecorator): + basename = "IfcAnnotation/Stair" + installed = None + + def decorate(self, objects): + segments = list(chain.from_iterable(self.get_segments(obj) for obj in objects)) + if not segments: + return + + region3d = self.context.region_data + points = self.flat_points(segments) + batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) + self.shader.bind() + self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) + self.shader.uniform_float('color', (1.0, 1.0, 1.0)) + batch.draw(self.shader) + + +class HiddenDecorator(MeshDecorator, BaseDecorator): + basename = "IfcAnnotation/Hidden" + installed = None + + def decorate(self, objects): + segments = list(chain.from_iterable(self.get_segments(obj) for obj in objects)) + if not segments: + return + + region3d = self.context.region_data + points = self.flat_points(segments) + batch = batch_for_shader(self.shader, 'LINES', {'pos': points}) + self.shader.bind() + self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) self.shader.uniform_float('color', (1.0, 1.0, 1.0)) - self.shader.uniform_float('angle', math.pi / 12) - self.shader.uniform_float('length', 16) batch.draw(self.shader) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 7fd0c5e456..6b59f51f99 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -362,8 +362,16 @@ def toggleDecorations(self, context): toggle = self.should_draw_decorations if toggle: decoration.DimensionDecorator.install(self, context) + decoration.EqualityDecorator.install(self, context) + decoration.LeaderDecorator.install(self, context) + decoration.StairDecorator.install(self, context) + decoration.HiddenDecorator.install(self, context) else: decoration.DimensionDecorator.uninstall() + decoration.EqualityDecorator.uninstall() + decoration.LeaderDecorator.uninstall() + decoration.StairDecorator.uninstall() + decoration.HiddenDecorator.uninstall() def getScenarios(self, context): From ec02c8600bf4f6c0fc1ff68e1733c1a06f4131ae Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 10/82] refactored shaders --- .../blenderbim/bim/decoration.py | 346 +++++++++--------- 1 file changed, 174 insertions(+), 172 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 5bccfd7a60..fdf9321d8b 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -25,10 +25,13 @@ class BaseDecorator(): VERT_GLSL = """ uniform mat4 viewMatrix; in vec3 pos; + in uint style; out vec4 gl_Position; + out uint vstyle; void main() { gl_Position = viewMatrix * vec4(pos, 1.0); + vstyle = style; } """ GEOM_GLSL = """ @@ -44,15 +47,43 @@ class BaseDecorator(): EndPrimitive(); } """ + FRAG_GLSL = """ - uniform vec3 color; out vec4 fragColor; void main() { - fragColor = vec4(color, 1.0); + fragColor = vec4(1.0, 1.0, 1.0, 1.0); } """ + LIB_GLSL = """ + void arrow_head(in vec2 p0, in vec2 p1, out vec2 head[3]) { + vec2 nose = normalize(p1 - p0) * ARROW_SIZE; + float c = cos(ARROW_ANGLE), s = sin(ARROW_ANGLE); + head[0] = nose; + head[1] = mat2(c, -s, +s, c) * nose; + head[2] = mat2(c, +s, -s, c) * nose; + } + + void circle_head(in vec2 p0, in vec2 p1, out vec2 head[CIRCLE_SEGS]) { + vec2 nose = normalize(p1 - p0) * CIRCLE_SIZE ; + float angle = PI / (CIRCLE_SEGS / 2); + for(int i = 0; i Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 11/82] decorations for Dim, Eq, Leader, Stair close #1177, #1176, #1158, #1150 --- .../blenderbim/bim/decoration.py | 258 ++++++++++++++---- 1 file changed, 201 insertions(+), 57 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index fdf9321d8b..54b0d985c9 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -66,19 +66,19 @@ class BaseDecorator(): } void circle_head(in vec2 p0, in vec2 p1, out vec2 head[CIRCLE_SEGS]) { - vec2 nose = normalize(p1 - p0) * CIRCLE_SIZE ; - float angle = PI / (CIRCLE_SEGS / 2); + float angle_d = PI * 2 / CIRCLE_SEGS; for(int i = 0; i Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 12/82] decorator for hidden close #1178 --- .../blenderbim/bim/decoration.py | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 54b0d985c9..9f8b93f326 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -82,6 +82,9 @@ class BaseDecorator(): #define ARROW_ANGLE PI / 12.0 #define ARROW_SIZE 16.0 #define CIRCLE_SIZE 8.0 + #define DASH_SIZE 16.0 + #define DASH_RATIO 0.5 + #define DASH_GAP 4.0 """ # class var for single handler @@ -162,7 +165,9 @@ class BaseDecorator(): - indices: 2-tuples of each segment verices' indices - styles: 0=internal, 1=beginning, 2=ending """ - pass + vertices = [obj.matrix_world @ v.co for v in obj.data.vertices] + indices = [e.vertices for e in obj.data.edges] + return vertices, indices, None def decorate(self, object): """perform actuall drawing stuff""" @@ -495,6 +500,72 @@ class HiddenDecorator(BaseDecorator): basename = "IfcAnnotation/Hidden" installed = None + GEOM_GLSL = """ + uniform vec2 winsize; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + + out float dist; + + void main() { + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; + vec2 segm = p1w - p0w; + vec2 gap = normalize(segm) * DASH_GAP; + vec4 gap_ndc = unwindowMatrix * vec4(gap, 0, 0); + + dist = 0; + gl_Position = p0 + gap_ndc * p0.w; + EmitVertex(); + dist = length(segm); + gl_Position = p1 - gap_ndc * p1.w; + EmitVertex(); + EndPrimitive(); + } + """ + + FRAG_GLSL = """ + in vec2 gl_FragCoord; + in float dist; + out vec4 fragColor; + + void main() { + float t = fract(dist / DASH_SIZE); + // fragColor = vec4(1.0, 1.0, 1.0, 1.0) * t; + if (t > DASH_RATIO) { + discard; + } else { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + } + """ + def decorate(self, obj): geom = self.get_mesh_geom(obj) self.draw_lines(obj, geom) + + def draw_lines(self, obj, geom): + region = self.context.region + region3d = self.context.region_data + + vertices, indices, styles = geom + + fmt = GPUVertFormat() + fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + + vbo = GPUVertBuf(len=len(vertices), format=fmt) + vbo.attr_fill(id="pos", data=vertices) + + ibo = GPUIndexBuf(type='LINES', seq=indices) + + batch = GPUBatch(type='LINES', buf=vbo, elem=ibo) + + self.shader.bind() + self.shader.uniform_float + self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) + self.shader.uniform_float("winsize", (region.width, region.height)) + batch.draw(self.shader) From ff0a7891bea3942deaa07bdff0fd28ad3fe1f8c0 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Mon, 21 Dec 2020 20:22:23 +0700 Subject: [PATCH 13/82] decorators code cleanup --- .../blenderbim/bim/decoration.py | 102 +++++++----------- 1 file changed, 39 insertions(+), 63 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 9f8b93f326..616dda8e93 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -14,10 +14,6 @@ from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertForma from gpu_extras.batch import batch_for_shader -class Shaders: - pass - - class BaseDecorator(): # base name of objects to decorate basename = "IfcAnnotation/Something" @@ -192,8 +188,7 @@ class BaseDecorator(): self.shader.bind() self.shader.uniform_float self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) - self.shader.uniform_float("windowW", region.width) - self.shader.uniform_float("windowH", region.height) + self.shader.uniform_float("winsize", (region.width, region.height)) batch.draw(self.shader) def draw_lines_styled(self, obj, geom): @@ -217,8 +212,7 @@ class BaseDecorator(): self.shader.bind() self.shader.uniform_float self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) - self.shader.uniform_float("windowW", region.width) - self.shader.uniform_float("windowH", region.height) + self.shader.uniform_float("winsize", (region.width, region.height)) batch.draw(self.shader) @@ -230,27 +224,26 @@ class DimensionDecorator(BaseDecorator): basename = "IfcAnnotation/Dimension" installed = None GEOM_GLSL = """ + uniform vec2 winsize; + layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; - uniform float windowW; - uniform float windowH; - void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - // converting NDC to/from window coords - float hw = windowW/2, hh= windowH/2; - mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); mat4 unwindowMatrix = inverse(windowMatrix); + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; vec2 head[3]; vec4 head_ndc[3]; arrow_head(p0w, p1w, head); for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; - vec4 nose = head_ndc[0]; + vec4 gap = head_ndc[0]; + // start edge arrow gl_Position = p0; EmitVertex(); gl_Position = p0 + head_ndc[1]; @@ -261,6 +254,7 @@ class DimensionDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); + // end edge arrow gl_Position = p1; EmitVertex(); gl_Position = p1 - head_ndc[1]; @@ -271,9 +265,10 @@ class DimensionDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); - gl_Position = p0 + nose; + // stem, adjusted for arrows + gl_Position = p0 + gap; EmitVertex(); - gl_Position = p1 - nose; + gl_Position = p1 - gap; EmitVertex(); EndPrimitive(); } @@ -367,30 +362,29 @@ class LeaderDecorator(BaseDecorator): installed = None GEOM_GLSL = """ + uniform vec2 winsize; + layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; in uint vstyle[]; - uniform float windowW; - uniform float windowH; - void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; uint s0 = vstyle[0], s1 = vstyle[1]; - // converting NDC to/from window coords - float hw = windowW/2, hh= windowH/2; - mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); mat4 unwindowMatrix = inverse(windowMatrix); + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; - vec4 nose1 = vec4(0); + vec4 gap1 = vec4(0); vec2 head[3]; vec4 head_ndc[3]; arrow_head(p0w, p1w, head); for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; + // end edge arrow for last segment if (s1 == 2u) { gl_Position = p1; EmitVertex(); @@ -402,13 +396,14 @@ class LeaderDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); - vec2 nose = normalize(p1w - p0w) * ARROW_SIZE; - nose1 = unwindowMatrix * vec4(nose, 0, 0) * p1.w; + vec2 gap = normalize(p1w - p0w) * ARROW_SIZE; + gap1 = unwindowMatrix * vec4(gap, 0, 0) * p1.w; } + // stem, adjusted for and arrow gl_Position = p0; EmitVertex(); - gl_Position = p1 - nose1; + gl_Position = p1 - gap1; EmitVertex(); EndPrimitive(); } @@ -429,25 +424,24 @@ class StairDecorator(BaseDecorator): installed = None GEOM_GLSL = """ + uniform vec2 winsize; + layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; in uint vstyle[]; - uniform float windowW; - uniform float windowH; - void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; uint s0 = vstyle[0], s1 = vstyle[1]; - // converting NDC to/from window coords - float hw = windowW/2, hh= windowH/2; - mat4 windowMatrix = mat4(hw, 0, 0, 0, 0, hh, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); mat4 unwindowMatrix = inverse(windowMatrix); + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; - vec4 nose0 = vec4(0), nose1 = vec4(0); + vec4 gap0 = vec4(0), gap1 = vec4(0); + // start edge circle for first segment if (s0 == 1u) { vec2 head[CIRCLE_SEGS]; circle_head(p0w, p1w, head); @@ -459,10 +453,11 @@ class StairDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); - vec2 nose = normalize(p1w - p0w) * CIRCLE_SIZE; - nose0 = unwindowMatrix * vec4(nose, 0, 0) * p0.w; + vec2 gap = normalize(p1w - p0w) * CIRCLE_SIZE; + gap0 = unwindowMatrix * vec4(gap, 0, 0) * p0.w; } + // end edge arrow for last segment if (s1 == 2u) { vec2 head[3]; vec4 head_ndc[3]; @@ -479,13 +474,14 @@ class StairDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); - vec2 nose = normalize(p1w - p0w) * ARROW_SIZE; - nose1 = unwindowMatrix * vec4(nose, 0, 0) * p1.w; + vec2 gap = normalize(p1w - p0w) * ARROW_SIZE; + gap1 = unwindowMatrix * vec4(gap, 0, 0) * p1.w; } - gl_Position = p0 + nose0; + // stem, adjusted for edge decoration + gl_Position = p0 + gap0; EmitVertex(); - gl_Position = p1 - nose1; + gl_Position = p1 - gap1; EmitVertex(); EndPrimitive(); } @@ -506,7 +502,7 @@ class HiddenDecorator(BaseDecorator): layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; - out float dist; + out float dist; // distance from starging point along segment void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; @@ -518,6 +514,8 @@ class HiddenDecorator(BaseDecorator): vec2 gap = normalize(segm) * DASH_GAP; vec4 gap_ndc = unwindowMatrix * vec4(gap, 0, 0); + // NB: something should be used to affect position, otherwise compiler eliminates winsize + dist = 0; gl_Position = p0 + gap_ndc * p0.w; EmitVertex(); @@ -547,25 +545,3 @@ class HiddenDecorator(BaseDecorator): def decorate(self, obj): geom = self.get_mesh_geom(obj) self.draw_lines(obj, geom) - - def draw_lines(self, obj, geom): - region = self.context.region - region3d = self.context.region_data - - vertices, indices, styles = geom - - fmt = GPUVertFormat() - fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') - - vbo = GPUVertBuf(len=len(vertices), format=fmt) - vbo.attr_fill(id="pos", data=vertices) - - ibo = GPUIndexBuf(type='LINES', seq=indices) - - batch = GPUBatch(type='LINES', buf=vbo, elem=ibo) - - self.shader.bind() - self.shader.uniform_float - self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) - self.shader.uniform_float("winsize", (region.width, region.height)) - batch.draw(self.shader) From 60dd74c64d023fdff4397ffc44b6ad9f90a948b3 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 15:55:57 +0100 Subject: [PATCH 14/82] bimtester: steps, better name for element class guid tests --- .../steps/{element_classes.py => element_classes_guid.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/ifcbimtester/bimtester/features/steps/{element_classes.py => element_classes_guid.py} (100%) diff --git a/src/ifcbimtester/bimtester/features/steps/element_classes.py b/src/ifcbimtester/bimtester/features/steps/element_classes_guid.py similarity index 100% rename from src/ifcbimtester/bimtester/features/steps/element_classes.py rename to src/ifcbimtester/bimtester/features/steps/element_classes_guid.py From de5dc40e63c79d0a6c9357bee100811eab16628b Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 15:56:26 +0100 Subject: [PATCH 15/82] bimtester: steps, add new test module for spatial structure --- .../bimtester/features/steps/spatial_structure.py | 10 ++++++++++ src/ifcbimtester/bimtester/features/steps/steps.py | 7 ------- 2 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 src/ifcbimtester/bimtester/features/steps/spatial_structure.py diff --git a/src/ifcbimtester/bimtester/features/steps/spatial_structure.py b/src/ifcbimtester/bimtester/features/steps/spatial_structure.py new file mode 100644 index 0000000000..ef96979639 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/spatial_structure.py @@ -0,0 +1,10 @@ +from behave import step + +from utils import IfcFile + + +@step("all buildings have an address") +def step_impl(context): + for building in IfcFile.get().by_type("IfcBuilding"): + if not building.BuildingAddress: + assert False, f'The building "{building.Name}" has no address.' diff --git a/src/ifcbimtester/bimtester/features/steps/steps.py b/src/ifcbimtester/bimtester/features/steps/steps.py index 9990a9ef97..86ee12d80f 100644 --- a/src/ifcbimtester/bimtester/features/steps/steps.py +++ b/src/ifcbimtester/bimtester/features/steps/steps.py @@ -122,10 +122,3 @@ def step_impl(context, ifc_class, attribute_name, attribute_value): if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value: return assert False - - -@step("all buildings have an address") -def step_impl(context): - for building in IfcFile.get().by_type("IfcBuilding"): - if not building.BuildingAddress: - assert False, f'The building "{building.Name}" has no address.' From 8e78ed332fb4e9ba4c6d5d558cdb5280219029b0 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 16:05:06 +0100 Subject: [PATCH 16/82] bimtester: steps, move project test into project test module --- src/ifcbimtester/bimtester/features/steps/project_setup.py | 6 ++++++ src/ifcbimtester/bimtester/features/steps/steps.py | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup.py b/src/ifcbimtester/bimtester/features/steps/project_setup.py index 9cb8af8732..8fbf128d52 100644 --- a/src/ifcbimtester/bimtester/features/steps/project_setup.py +++ b/src/ifcbimtester/bimtester/features/steps/project_setup.py @@ -67,3 +67,9 @@ def get_subcontext(identifier, type, target_view): assert False, "The subcontext with identifier {}, type {}, and target view {} could not be found".format( identifier, type, target_view ) + + +@step('the project has a {attribute_name} attribute with a value of "{attribute_value}"') +def step_impl(context, attribute_name, attribute_value): + project = IfcFile.get().by_type("IfcProject")[0] + assert getattr(project, attribute_name) == attribute_value diff --git a/src/ifcbimtester/bimtester/features/steps/steps.py b/src/ifcbimtester/bimtester/features/steps/steps.py index 86ee12d80f..583ee0c6d4 100644 --- a/src/ifcbimtester/bimtester/features/steps/steps.py +++ b/src/ifcbimtester/bimtester/features/steps/steps.py @@ -109,12 +109,6 @@ def step_impl(context, ifc_class, qto_name, quantity_name): use_step_matcher("parse") -@step('the project has a {attribute_name} attribute with a value of "{attribute_value}"') -def step_impl(context, attribute_name, attribute_value): - project = IfcFile.get().by_type("IfcProject")[0] - assert getattr(project, attribute_name) == attribute_value - - @step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"') def step_impl(context, ifc_class, attribute_name, attribute_value): elements = IfcFile.get().by_type(ifc_class) From 1a48472b19c6199663b2f13a0f97eb8143c905a2 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 16:07:08 +0100 Subject: [PATCH 17/82] bimtester: steps, sort them according expression matcher --- .../bimtester/features/steps/steps.py | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/ifcbimtester/bimtester/features/steps/steps.py b/src/ifcbimtester/bimtester/features/steps/steps.py index 583ee0c6d4..f63a470fd2 100644 --- a/src/ifcbimtester/bimtester/features/steps/steps.py +++ b/src/ifcbimtester/bimtester/features/steps/steps.py @@ -18,6 +18,34 @@ def step_impl(context, ifc_class, pattern): assert False +@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity") +def step_impl(context, ifc_class, qto_name, quantity_name): + elements = IfcFile.get().by_type(ifc_class) + for element in elements: + is_successful = False + if not element.IsDefinedBy: + assert False + for relationship in element.IsDefinedBy: + if relationship.RelatingPropertyDefinition.Name == qto_name: + for quantity in relationship.RelatingPropertyDefinition.Quantities: + if quantity.Name == quantity_name: + is_successful = True + if not is_successful: + assert False + + +@step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"') +def step_impl(context, ifc_class, attribute_name, attribute_value): + elements = IfcFile.get().by_type(ifc_class) + for element in elements: + if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value: + return + assert False + + +# ------------------------------------------------------------------------ +# STEPS with Regular Expression Matcher ("re") +# ------------------------------------------------------------------------ use_step_matcher("re") @@ -85,34 +113,3 @@ def step_impl(context, ifc_class, attributes, list_file): attribute_values.append(getattr(element, attribute)) if attribute_values not in values: assert False, f"Failed at element {element.GlobalId}" - - -use_step_matcher("parse") - - -@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity") -def step_impl(context, ifc_class, qto_name, quantity_name): - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - is_successful = False - if not element.IsDefinedBy: - assert False - for relationship in element.IsDefinedBy: - if relationship.RelatingPropertyDefinition.Name == qto_name: - for quantity in relationship.RelatingPropertyDefinition.Quantities: - if quantity.Name == quantity_name: - is_successful = True - if not is_successful: - assert False - - -use_step_matcher("parse") - - -@step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"') -def step_impl(context, ifc_class, attribute_name, attribute_value): - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value: - return - assert False From dafdd06b2e79eda3d1a6130b952d641fed5239a7 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 16:32:36 +0100 Subject: [PATCH 18/82] bimtester: steps, split steps testmodule --- .../{steps.py => attributes_eleclasses.py} | 43 ------------------- .../features/steps/attributes_psets.py | 36 ++++++++++++++++ .../features/steps/attributes_qsets.py | 19 ++++++++ 3 files changed, 55 insertions(+), 43 deletions(-) rename src/ifcbimtester/bimtester/features/steps/{steps.py => attributes_eleclasses.py} (59%) create mode 100644 src/ifcbimtester/bimtester/features/steps/attributes_psets.py create mode 100644 src/ifcbimtester/bimtester/features/steps/attributes_qsets.py diff --git a/src/ifcbimtester/bimtester/features/steps/steps.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py similarity index 59% rename from src/ifcbimtester/bimtester/features/steps/steps.py rename to src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py index f63a470fd2..43cee4e9e5 100644 --- a/src/ifcbimtester/bimtester/features/steps/steps.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py @@ -18,22 +18,6 @@ def step_impl(context, ifc_class, pattern): assert False -@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity") -def step_impl(context, ifc_class, qto_name, quantity_name): - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - is_successful = False - if not element.IsDefinedBy: - assert False - for relationship in element.IsDefinedBy: - if relationship.RelatingPropertyDefinition.Name == qto_name: - for quantity in relationship.RelatingPropertyDefinition.Quantities: - if quantity.Name == quantity_name: - is_successful = True - if not is_successful: - assert False - - @step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"') def step_impl(context, ifc_class, attribute_name, attribute_value): elements = IfcFile.get().by_type(ifc_class) @@ -57,33 +41,6 @@ def step_impl(context, ifc_class, attribute): assert False -@step("all (?P.*) elements have an? (?P.*\..*) property") -def step_impl(context, ifc_class, property_path): - pset_name, property_name = property_path.split(".") - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - if not IfcFile.get_property(element, pset_name, property_name): - assert False - - -@step( - 'all (?P.*) elements have an? (?P.*\..*) property value matching the pattern "(?P.*)"' -) -def step_impl(context, ifc_class, property_path, pattern): - import re - - pset_name, property_name = property_path.split(".") - elements = IfcFile.get().by_type(ifc_class) - for element in elements: - prop = IfcFile.get_property(element, pset_name, property_name) - if not prop: - assert False - # For now, we only check single values - if prop.is_a("IfcPropertySingleValue"): - if not (prop.NominalValue and re.search(pattern, prop.NominalValue.wrappedValue)): - assert False - - @step('all (?P.*) elements have an? (?P.*) matching the pattern "(?P.*)"') def step_impl(context, ifc_class, attribute, pattern): import re diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_psets.py b/src/ifcbimtester/bimtester/features/steps/attributes_psets.py new file mode 100644 index 0000000000..9d46a6cc14 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/attributes_psets.py @@ -0,0 +1,36 @@ +from behave import step + +from utils import IfcFile + + +# ------------------------------------------------------------------------ +# STEPS with Regular Expression Matcher ("re") +# ------------------------------------------------------------------------ +use_step_matcher("re") + + +@step("all (?P.*) elements have an? (?P.*\..*) property") +def step_impl(context, ifc_class, property_path): + pset_name, property_name = property_path.split(".") + elements = IfcFile.get().by_type(ifc_class) + for element in elements: + if not IfcFile.get_property(element, pset_name, property_name): + assert False + + +@step( + 'all (?P.*) elements have an? (?P.*\..*) property value matching the pattern "(?P.*)"' +) +def step_impl(context, ifc_class, property_path, pattern): + import re + + pset_name, property_name = property_path.split(".") + elements = IfcFile.get().by_type(ifc_class) + for element in elements: + prop = IfcFile.get_property(element, pset_name, property_name) + if not prop: + assert False + # For now, we only check single values + if prop.is_a("IfcPropertySingleValue"): + if not (prop.NominalValue and re.search(pattern, prop.NominalValue.wrappedValue)): + assert False diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_qsets.py b/src/ifcbimtester/bimtester/features/steps/attributes_qsets.py new file mode 100644 index 0000000000..85a2f3716c --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/attributes_qsets.py @@ -0,0 +1,19 @@ +from behave import step + +from utils import IfcFile + + +@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity") +def step_impl(context, ifc_class, qto_name, quantity_name): + elements = IfcFile.get().by_type(ifc_class) + for element in elements: + is_successful = False + if not element.IsDefinedBy: + assert False + for relationship in element.IsDefinedBy: + if relationship.RelatingPropertyDefinition.Name == qto_name: + for quantity in relationship.RelatingPropertyDefinition.Quantities: + if quantity.Name == quantity_name: + is_successful = True + if not is_successful: + assert False From 2a6efaaaa4b162f96fff89959a5e83ef13c563e4 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 16:57:38 +0100 Subject: [PATCH 19/82] bimtester: step, prepare attributes eleclasses for translation --- .../features/steps/attributes_eleclasses.py | 4 +- .../steps/attributes_eleclasses_methods.py | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py index 43cee4e9e5..c39467568f 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py @@ -1,11 +1,13 @@ from behave import step +from attributes_eleclasses_methods import no_element_class_ele from utils import IfcFile @step("there are no {ifc_class} elements because {reason}") def step_impl(context, ifc_class, reason): - assert len(IfcFile.get().by_type(ifc_class)) == 0 + switch_locale(context.localedir, "en") + no_element_class_ele(context, ifc_class, reason) @step('all {ifc_class} elements have a name matching the pattern "{pattern}"') diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py new file mode 100644 index 0000000000..ddd1238d9f --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py @@ -0,0 +1,41 @@ +import gettext # noqa +from utils import assert_elements +from utils import IfcFile + + +def no_element_class_ele(context, ifc_class, reason): + + context.falseelems = [] + context.falseguids = [] + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + + + if context.elemcount == 0 and context.falsecount == 0: + return # Test OK, thus we can not use the assert_elements method + elif context.falsecount == context.elemcount: + assert False, ( + _("All {elemcount} elements in the file are {ifc_class}.") + .format( + elemcount=context.elemcount, + ifc_class=ifc_class + ) + ) + elif context.falsecount > 0 and fcontext.alsecount < context.elemcount: + assert False, ( + _("{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}") + .format( + falsecount=context.falsecount, + elemcount=context.elemcount, + ifc_class=ifc_class, + falseelems=context.falseelems, + ) + ) + else: + assert False, _("Error in falsecount, something went wrong.") From edd6e74813c2259d1b1c77fa01e814485ae29fd6 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 17:01:28 +0100 Subject: [PATCH 20/82] bimtester: steps, add element class attributes test --- .../features/steps/attributes_eleclasses.py | 7 +++ .../steps/attributes_eleclasses_methods.py | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py index c39467568f..0112e170a9 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py @@ -1,5 +1,6 @@ from behave import step +from attributes_eleclasses_methods import all_element_attribs_have_a_value from attributes_eleclasses_methods import no_element_class_ele from utils import IfcFile @@ -10,6 +11,12 @@ def step_impl(context, ifc_class, reason): no_element_class_ele(context, ifc_class, reason) +@step('all {ifc_class} elements class attributes have a value') +def step_impl(context, ifc_class): + switch_locale(context.localedir, "en") + all_element_attribs_have_a_value(context, ifc_class) + + @step('all {ifc_class} elements have a name matching the pattern "{pattern}"') def step_impl(context, ifc_class, pattern): import re diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py index ddd1238d9f..b48ab29438 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py @@ -1,4 +1,5 @@ import gettext # noqa + from utils import assert_elements from utils import IfcFile @@ -39,3 +40,46 @@ def no_element_class_ele(context, ifc_class, reason): ) else: assert False, _("Error in falsecount, something went wrong.") + + +def all_element_attribs_have_a_value(context, ifc_class): + + from ifcopenshell.ifcopenshell_wrapper import schema_by_name + # schema = schema_by_name("IFC2X3") + schema = schema_by_name(IfcFile.get().schema) + class_attributes = [] + for cl_attrib in schema.declaration_by_name(ifc_class).all_attributes(): + class_attributes.append(cl_attrib.name()) + # print(class_attributes) + + context.falseelems = [] + context.falseguids = [] + context.falseprops = {} + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + failed_attribs = [] + elem_failed = False + for cl_attrib in class_attributes: + attrib_value = getattr(elem, cl_attrib) + if not attrib_value: + elem_failed = True + failed_attribs.append(cl_attrib) + # print(attrib_value) + if elem_failed is True: + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + context.falseprops[elem.id()] = failed_attribs + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + assert_elements( + ifc_class, + context.elemcount, + context.falsecount, + context.falseelems, + message_all_falseelems=_("For all {elemcount} {ifc_class} elements at least one of these class attributes {parameter} has no value."), + message_some_falseelems=_("For the following {falsecount} out of {elemcount} {ifc_class} elements at least one of these class attributes {parameter} has no value: {falseelems}"), + message_no_elems=_("There are no {ifc_class} elements in the IFC file."), + parameter=failed_attribs + ) From 7d138b8f72ebfa0bb3657977f8417b2b4e443726 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 17:09:46 +0100 Subject: [PATCH 21/82] bimtester: steps, add missing import --- .../bimtester/features/steps/attributes_eleclasses.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py index 0112e170a9..d63ca60d2f 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py @@ -3,6 +3,7 @@ from behave import step from attributes_eleclasses_methods import all_element_attribs_have_a_value from attributes_eleclasses_methods import no_element_class_ele from utils import IfcFile +from utils import switch_locale @step("there are no {ifc_class} elements because {reason}") From 2226d5a565c8f7a5af0a87b2c465014e3aef923f Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 21 Dec 2020 17:14:12 +0100 Subject: [PATCH 22/82] bimtester: steps, remove a newline added by accident --- .../bimtester/features/steps/attributes_eleclasses_methods.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py index b48ab29438..03cfd5f437 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py @@ -17,7 +17,6 @@ def no_element_class_ele(context, ifc_class, reason): context.elemcount = len(elements) context.falsecount = len(context.falseelems) - if context.elemcount == 0 and context.falsecount == 0: return # Test OK, thus we can not use the assert_elements method elif context.falsecount == context.elemcount: From ee42703f9f56e5a3c889bb37c4e6a1319e80311a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 22 Dec 2020 16:22:29 +1100 Subject: [PATCH 23/82] Implement delete BCF viewpoint --- src/bcf/bcf/bcfxml.py | 23 +++++++++++++------ .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 18 ++++++++++++++- src/ifcblenderexport/blenderbim/bim/ui.py | 15 ++++++++---- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 40069ceb15..28cea52ca2 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -206,6 +206,8 @@ class BcfXml: self.document = minidom.Document() root = self._create_element(self.document, "Markup") + self.write_header(topic.header, root) + topic_el = self._create_element( root, "Topic", @@ -216,10 +218,22 @@ class BcfXml: }, ) + for reference_link in topic.reference_links: + self._create_element(topic_el, "ReferenceLink", text=reference_link) + text_map = { "Title": topic.title, "Priority": topic.priority, "Index": topic.index, + } + for key, value in text_map.items(): + if value: + self._create_element(topic_el, key, text=value) + + for label in topic.labels: + self._create_element(topic_el, "Labels", text=label) + + text_map = { "CreationDate": topic.creation_date, "CreationAuthor": topic.creation_author, "ModifiedDate": topic.modified_date, @@ -233,10 +247,6 @@ class BcfXml: if value: self._create_element(topic_el, key, text=value) - for reference_link in topic.reference_links: - self._create_element(topic_el, "ReferenceLink", text=reference_link) - for label in topic.labels: - self._create_element(topic_el, "Labels", text=label) if topic.bim_snippet: bim_snippet = self._create_element( topic_el, @@ -254,7 +264,6 @@ class BcfXml: for related_topic in topic.related_topics: self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid}) - self.write_header(topic.header, root) self.write_comments(topic.comments, root) self.write_viewpoints(topic.viewpoints, root, topic) @@ -334,7 +343,7 @@ class BcfXml: def write_viewpoint(self, viewpoint, topic): document = minidom.Document() - root = self._create_element(document, "VisualizationInfo") + root = self._create_element(document, "VisualizationInfo", { "Guid": viewpoint.guid }) self.write_viewpoint_components(viewpoint, root) self.write_viewpoint_orthogonal_camera(viewpoint, root) self.write_viewpoint_perspective_camera(viewpoint, root) @@ -354,7 +363,7 @@ class BcfXml: "ViewSetupHints", { "SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible, - "SpaceBoundiresVisible": viewpoint.components.view_setup_hints.space_boundaries_visible, + "SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible, "OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible, }, ) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 8cdb13edd9..f80dd23fe3 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -36,6 +36,7 @@ if bpy is not None: operator.SaveBcfProject, operator.AddBcfTopic, operator.ViewBcfTopic, + operator.RemoveBcfViewpoint, operator.ActivateBcfViewpoint, operator.OpenUri, operator.OpenBcfReferenceLink, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 15aaee35b6..f5c6ba7efe 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -737,6 +737,22 @@ class ViewBcfTopic(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfViewpoint(bpy.types.Operator): + bl_idname = "bim.remove_bcf_viewpoint" + bl_label = "Remove BCF Viewpoint" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + viewpoint_guid = blender_topic.viewpoints + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_viewpoint(viewpoint_guid, topic) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + print(bcfxml.filepath) + return {"FINISHED"} + + class ActivateBcfViewpoint(bpy.types.Operator): bl_idname = "bim.activate_bcf_viewpoint" bl_label = "Activate BCF Viewpoint" @@ -745,7 +761,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): bcfxml = bcfstore.BcfStore.get_bcfxml() props = bpy.context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.guid] + topic = bcfxml.topics[blender_topic.name] if not topic.viewpoints: return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 01eb0d76a3..b159bb95ab 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1696,6 +1696,7 @@ class BIM_PT_bcf(Panel): row = layout.row() row.prop(topic, "viewpoints") row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") + row.operator("bim.remove_bcf_viewpoint", icon="X", text="") col = layout.column(align=True) if topic.type: @@ -1757,8 +1758,8 @@ class BIM_PT_bcf_metadata(Panel): op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) box.label(text=f.date) - #box.label(text=f.ifc_project) - #box.label(text=f.ifc_spatial_structure_element) + # box.label(text=f.ifc_project) + # box.label(text=f.ifc_spatial_structure_element) if topic.reference_links: layout.label(text="Reference Links:") @@ -1850,7 +1851,7 @@ class BIM_PT_bcf_comments(Panel): while words and total_line_chars < props.comment_text_width: word = words.pop(0) line_words.append(word) - total_line_chars += len(word) + 1 # 1 is for the space + total_line_chars += len(word) + 1 # 1 is for the space box.label(text=" ".join(line_words)) box.separator() @@ -2199,14 +2200,16 @@ class BIM_UL_clash_sets(bpy.types.UIList): else: layout.label(text="", translate=False) + class BIM_UL_smart_groups(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.label(text=str(item.number), translate=False, icon='NONE', icon_value=0) + layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0) else: layout.label(text="", translate=False) + class BIM_UL_constraints(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data @@ -2469,6 +2472,7 @@ class BIM_PT_qto_utilities(Panel): row = layout.row(align=True) row.operator("bim.calculate_object_volumes") + class BIM_PT_clash_manager(Panel): bl_idname = "BIM_PT_clash_manager" bl_label = "Clash Manager" @@ -2503,11 +2507,12 @@ class BIM_PT_clash_manager(Panel): row = layout.row(align=True) row.operator("bim.load_smart_groups_for_active_clash_set") - layout.template_list('BIM_UL_smart_groups', '', props, 'smart_clash_groups', props, 'active_smart_group_index') + layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index") row = layout.row(align=True) row.operator("bim.select_smart_group") + class BIM_PT_misc_utilities(Panel): bl_idname = "BIM_PT_misc_utilities" bl_label = "Miscellaneous" From 6a5d255c2f541209380d4ab29d21e71d0934130a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 22 Dec 2020 18:23:43 +1100 Subject: [PATCH 24/82] Implement delete BCF comment --- src/bcf/bcf/bcfxml.py | 8 ++++++-- src/bcf/bcf/data.py | 2 +- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 19 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 14 ++++++++------ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 28cea52ca2..2b03be6812 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -438,9 +438,11 @@ class BcfXml: return for bitmap in viewpoint.bitmaps: bitmap_el = self._create_element(parent, "Bitmap") - text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference, "Height": bitmap.height} + + text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference} for key, value in text_map.items(): self._create_element(bitmap_el, key, text=value) + location_el = self._create_element(bitmap_el, "Location") self.write_vector(location_el, bitmap.location) normal_el = self._create_element(bitmap_el, "Normal") @@ -448,6 +450,8 @@ class BcfXml: up_el = self._create_element(bitmap_el, "Up") self.write_vector(up_el, bitmap.up) + self._create_element(bitmap_el, "Height", text=bitmap.height) + def write_vector(self, parent, from_obj): self._create_element(parent, "X", text=from_obj.x) self._create_element(parent, "Y", text=from_obj.y) @@ -631,7 +635,7 @@ class BcfXml: for item in visinfo["Bitmap"]: bitmap = bcf.data.Bitmap() bitmap.reference = item["Reference"] - bitmap.bitmap_type = item["Bitmap"].lower() + bitmap.bitmap_type = item["Bitmap"].upper() self.set_vector(bitmap.location, item["Location"]) self.set_vector(bitmap.normal, item["Normal"]) self.set_vector(bitmap.up, item["Up"]) diff --git a/src/bcf/bcf/data.py b/src/bcf/bcf/data.py index 0be7560e55..7fd23638be 100644 --- a/src/bcf/bcf/data.py +++ b/src/bcf/bcf/data.py @@ -156,7 +156,7 @@ class Bitmap: def __init__(self): self.reference = "" # Only in BCF-XML self.bitmap_data = None # Only in BCF-API - self.bitmap_type = "png" # Enum of png or jpg + self.bitmap_type = "PNG" # Enum of png or jpg self.location = Point() self.normal = Direction() self.up = Direction() diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index f80dd23fe3..017f5a17e9 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -37,6 +37,7 @@ if bpy is not None: operator.AddBcfTopic, operator.ViewBcfTopic, operator.RemoveBcfViewpoint, + operator.RemoveBcfComment, operator.ActivateBcfViewpoint, operator.OpenUri, operator.OpenBcfReferenceLink, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f5c6ba7efe..69ce23f783 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -622,6 +622,8 @@ class LoadBcfComments(bpy.types.Operator): bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml.get_comments(self.topic_guid) blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid) + while len(blender_topic.comments) > 0: + blender_topic.comments.remove(0) for comment in bcfxml.topics[self.topic_guid].comments.values(): new = blender_topic.comments.add() data_map = { @@ -694,6 +696,7 @@ class EditBcfTopic(bpy.types.Operator): topic.topic_type = blender_topic.type or None bcfxml.edit_topic(topic) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic return {"FINISHED"} @@ -749,6 +752,22 @@ class RemoveBcfViewpoint(bpy.types.Operator): topic = bcfxml.topics[blender_topic.name] bcfxml.delete_viewpoint(viewpoint_guid, topic) props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + +class RemoveBcfComment(bpy.types.Operator): + bl_idname = "bim.remove_bcf_comment" + bl_label = "Remove BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_comment(self.comment_guid, topic) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) print(bcfxml.filepath) return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index b159bb95ab..48afe4913d 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1837,13 +1837,16 @@ class BIM_PT_bcf_comments(Panel): topic = props.topics[props.active_topic_index] for comment in topic.comments: box = self.layout.box() - box.separator() + author_text = "{} ({})".format(comment.author, comment.date) if comment.modified_author: author_text = "*{} ({})".format(comment.modified_author, comment.modified_date) - box.label(text=author_text, icon="WORDWRAP_ON") - box.separator() - box.scale_y = 0.5 + row = box.row(align=True) + row.label(text=author_text, icon="WORDWRAP_ON") + row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name + + col = box.column(align=True) + col.scale_y = 0.8 words = comment.comment.split() while words: total_line_chars = 0 @@ -1852,8 +1855,7 @@ class BIM_PT_bcf_comments(Panel): word = words.pop(0) line_words.append(word) total_line_chars += len(word) + 1 # 1 is for the space - box.label(text=" ".join(line_words)) - box.separator() + col.label(text=" ".join(line_words)) class BIM_PT_qa(Panel): From 6f84bc6ce5a0940e2caadc6b2fc7c5fdb1b64fd2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 22 Dec 2020 18:41:51 +1100 Subject: [PATCH 25/82] Implement BCF add comment --- src/bcf/bcf/bcfxml.py | 5 ++-- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 24 ++++++++++++++++++- src/ifcblenderexport/blenderbim/bim/prop.py | 2 ++ src/ifcblenderexport/blenderbim/bim/ui.py | 7 ++++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 2b03be6812..c1291283f7 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -304,14 +304,15 @@ class BcfXml: if comment.viewpoint: self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid}) - def add_comment(self, comment=None): + def add_comment(self, topic, comment=None): if comment is None: comment = bcf.data.Comment() if not comment.guid: comment.guid = str(uuid.uuid4()) if not comment.comment: comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'." - self.edit_comment(comment) + topic.comments[comment.guid] = comment + self.edit_comment(comment, topic) def edit_comment(self, comment, topic): if not comment.date: diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 017f5a17e9..5a956f6159 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -38,6 +38,7 @@ if bpy is not None: operator.ViewBcfTopic, operator.RemoveBcfViewpoint, operator.RemoveBcfComment, + operator.AddBcfComment, operator.ActivateBcfViewpoint, operator.OpenUri, operator.OpenBcfReferenceLink, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 69ce23f783..1f11523145 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1,5 +1,6 @@ import os import bpy +import bcf import uuid import time import json @@ -766,7 +767,28 @@ class RemoveBcfComment(bpy.types.Operator): blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] bcfxml.delete_comment(self.comment_guid, topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + return {"FINISHED"} + + +class AddBcfComment(bpy.types.Operator): + bl_idname = "bim.add_bcf_comment" + bl_label = "Add BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.comment: + return {"FINISHED"} + comment = bcf.data.Comment() + comment.comment = blender_topic.comment + if blender_topic.has_related_viewpoint and blender_topic.viewpoints: + comment.viewpoint = bcf.data.Viewpoint() + comment.viewpoint.guid = blender_topic.viewpoints + bcfxml.add_comment(topic, comment) bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) print(bcfxml.filepath) return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 6b59f51f99..4b83aa25bd 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1006,6 +1006,8 @@ class BcfTopic(PropertyGroup): related_topics: CollectionProperty(name="Related Topics", type=StrProperty) comments: CollectionProperty(name="Comments", type=BcfComment) is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) + comment: StringProperty(default="", name="Comment") + has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) class PropertySetTemplate(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 48afe4913d..43fdf91170 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1857,6 +1857,13 @@ class BIM_PT_bcf_comments(Panel): total_line_chars += len(word) + 1 # 1 is for the space col.label(text=" ".join(line_words)) + row = layout.row() + row.prop(topic, "comment") + row = layout.row() + row.prop(topic, "has_related_viewpoint") + row = layout.row() + row.operator("bim.add_bcf_comment") + class BIM_PT_qa(Panel): bl_label = "BIMTester Quality Auditing" From 8baf9b056d0cb51cbfee0914a811f12d2157f3f6 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 22 Dec 2020 18:47:50 +0700 Subject: [PATCH 26/82] decorators cleanup --- .../blenderbim/bim/decoration.py | 121 ++++++++---------- 1 file changed, 53 insertions(+), 68 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 616dda8e93..8393cdbbbd 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -21,13 +21,13 @@ class BaseDecorator(): VERT_GLSL = """ uniform mat4 viewMatrix; in vec3 pos; - in uint style; + in uint topo; out vec4 gl_Position; - out uint vstyle; + out uint type; void main() { gl_Position = viewMatrix * vec4(pos, 1.0); - vstyle = style; + type = topo; } """ GEOM_GLSL = """ @@ -129,16 +129,22 @@ class BaseDecorator(): 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): - """parse path geometry into line segments - returns: - - vertices: 3-tuples of coords - - indices: 2-tuples of each segment verices' indices - - styles: 0=internal, 1=beginning, 2=ending + def get_path_geom(self, obj, topo=True): + """Parses path geometry into line segments + Args: + obj: Blender object with data of type Curve + topo: bool; if types of vertices are needed + Returns: + vertices: 3-tuples of coords + indices: 2-tuples of each segment verices' indices + topology: types of vertices + 0: internal + 1: beginning + 2: ending """ vertices = [] indices = [] - styles = [] + topology = [] idx = 0 for spline in obj.data.splines: @@ -146,64 +152,46 @@ class BaseDecorator(): points = [obj.matrix_world @ p.co for p in spline_points] cnt = len(points) vertices.extend(p[:3] for p in points) - styles.append(1) - styles.extend([0] * max(0, cnt - 2)) - styles.append(2) + if topo: + topology.append(1) + topology.extend([0] * max(0, cnt - 2)) + topology.append(2) indices.extend((idx+i, idx+i+1) for i in range(cnt-1)) idx += cnt - return vertices, indices, styles + return vertices, indices, topology def get_mesh_geom(self, obj): - """parse mesh geometry into line segments - returns: - - vertices: 3-tuples of coords - - indices: 2-tuples of each segment verices' indices - - styles: 0=internal, 1=beginning, 2=ending + """Parses mesh geometry into line segments + + Args: + obj: Blender object with data of type Mesh + + Returns: + vertices: 3-tuples of coords + indices: 2-tuples of each segment verices' indices """ vertices = [obj.matrix_world @ v.co for v in obj.data.vertices] indices = [e.vertices for e in obj.data.edges] - return vertices, indices, None + return vertices, indices def decorate(self, object): """perform actuall drawing stuff""" raise NotImplementedError() - def draw_lines(self, obj, geom): + def draw_lines(self, obj, vertices, indices, topology=None): region = self.context.region region3d = self.context.region_data - vertices, indices, styles = geom - fmt = GPUVertFormat() fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + if topology: + fmt.attr_add(id="topo", comp_type='U8', len=1, fetch_mode='INT') vbo = GPUVertBuf(len=len(vertices), format=fmt) vbo.attr_fill(id="pos", data=vertices) - - ibo = GPUIndexBuf(type='LINES', seq=indices) - - batch = GPUBatch(type='LINES', buf=vbo, elem=ibo) - - self.shader.bind() - self.shader.uniform_float - self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) - self.shader.uniform_float("winsize", (region.width, region.height)) - batch.draw(self.shader) - - def draw_lines_styled(self, obj, geom): - region = self.context.region - region3d = self.context.region_data - - vertices, indices, styles = geom - - fmt = GPUVertFormat() - fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') - fmt.attr_add(id="style", comp_type='U8', len=1, fetch_mode='INT') - - vbo = GPUVertBuf(len=len(vertices), format=fmt) - vbo.attr_fill(id="pos", data=vertices) - vbo.attr_fill(id="style", data=styles) + if topology: + vbo.attr_fill(id="topo", data=topology) ibo = GPUIndexBuf(type='LINES', seq=indices) @@ -280,14 +268,13 @@ class DimensionDecorator(BaseDecorator): self.dpi = context.preferences.system.dpi def decorate(self, obj): - geom = self.get_path_geom(obj) - self.draw_lines(obj, geom) - self.draw_labels(obj, geom) + verts, idxs, _ = self.get_path_geom(obj, topo=False) + self.draw_lines(obj, verts, idxs) + self.draw_labels(obj, verts, idxs) - def draw_labels(self, obj, geom): + def draw_labels(self, obj, vertices, indices): region = self.context.region region3d = self.context.region_data - vertices, indices, _ = geom for i0, i1 in indices: v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) @@ -341,10 +328,9 @@ class EqualityDecorator(DimensionDecorator): basename = "IfcAnnotation/Equal" installed = None - def draw_labels(self, obj, geom): + def draw_labels(self, obj, vertices, indices): region = self.context.region region3d = self.context.region_data - vertices, indices, _ = geom for i0, i1 in indices: v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) @@ -366,11 +352,11 @@ class LeaderDecorator(BaseDecorator): layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; - in uint vstyle[]; + in uint type[]; void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - uint s0 = vstyle[0], s1 = vstyle[1]; + uint t0 = type[0], t1 = type[1]; mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); mat4 unwindowMatrix = inverse(windowMatrix); @@ -385,7 +371,7 @@ class LeaderDecorator(BaseDecorator): for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; // end edge arrow for last segment - if (s1 == 2u) { + if (t1 == 2u) { gl_Position = p1; EmitVertex(); gl_Position = p1 - head_ndc[1]; @@ -410,8 +396,8 @@ class LeaderDecorator(BaseDecorator): """ def decorate(self, obj): - geom = self.get_path_geom(obj) - self.draw_lines_styled(obj, geom) + verts, idxs, topo = self.get_path_geom(obj) + self.draw_lines(obj, verts, idxs, topo) class StairDecorator(BaseDecorator): @@ -428,11 +414,11 @@ class StairDecorator(BaseDecorator): layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; - in uint vstyle[]; + in uint type[]; void main() { vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - uint s0 = vstyle[0], s1 = vstyle[1]; + uint t0 = type[0], t1 = type[1]; mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); mat4 unwindowMatrix = inverse(windowMatrix); @@ -442,7 +428,7 @@ class StairDecorator(BaseDecorator): vec4 gap0 = vec4(0), gap1 = vec4(0); // start edge circle for first segment - if (s0 == 1u) { + if (t0 == 1u) { vec2 head[CIRCLE_SEGS]; circle_head(p0w, p1w, head); for(int i=0; i DASH_RATIO) { discard; } else { @@ -543,5 +528,5 @@ class HiddenDecorator(BaseDecorator): """ def decorate(self, obj): - geom = self.get_mesh_geom(obj) - self.draw_lines(obj, geom) + verts, idxs = self.get_mesh_geom(obj) + self.draw_lines(obj, verts, idxs) From a5530acfd7f656530474f42a3038da660d4f1b18 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 22 Dec 2020 19:27:44 +0700 Subject: [PATCH 27/82] fixed Hidden decotator in edit mode --- .../blenderbim/bim/decoration.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 8393cdbbbd..661d544735 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -6,6 +6,7 @@ from itertools import chain from bpy.types import SpaceView3D from mathutils import Vector, Matrix import bpy +import bmesh import blf from bpy_extras.view3d_utils import location_3d_to_region_2d import gpu @@ -175,6 +176,22 @@ class BaseDecorator(): indices = [e.vertices for e in obj.data.edges] return vertices, indices + def get_editmesh_geom(self, obj): + """Parses editmode mesh geometry into line segments + """ + mesh = bmesh.from_edit_mesh(obj.data) + vertices = [] + indices = [] + idx = 0 + + for edge in mesh.edges: + vertices.extend(edge.verts) + indices.append((idx, idx+1)) + idx += 2 + vertices = [obj.matrix_world @ v.co for v in vertices] + + return vertices, indices + def decorate(self, object): """perform actuall drawing stuff""" raise NotImplementedError() @@ -528,5 +545,8 @@ class HiddenDecorator(BaseDecorator): """ def decorate(self, obj): - verts, idxs = self.get_mesh_geom(obj) + if obj.data.is_editmode: + verts, idxs = self.get_editmesh_geom(obj) + else: + verts, idxs = self.get_mesh_geom(obj) self.draw_lines(obj, verts, idxs) From 7a7caffaaec552b8c63ded84518858141761c066 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 22 Dec 2020 19:35:29 +0700 Subject: [PATCH 28/82] fix #1198 stair decorator arrow shape --- .../blenderbim/bim/decoration.py | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 661d544735..30c644008b 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -54,9 +54,9 @@ class BaseDecorator(): """ LIB_GLSL = """ - void arrow_head(in vec2 p0, in vec2 p1, out vec2 head[3]) { - vec2 nose = normalize(p1 - p0) * ARROW_SIZE; - float c = cos(ARROW_ANGLE), s = sin(ARROW_ANGLE); + void arrow_head(in vec2 p0, in vec2 p1, in float size, in float angle, out vec2 head[3]) { + vec2 nose = normalize(p1 - p0) * size; + float c = cos(angle), s = sin(angle); head[0] = nose; head[1] = mat2(c, -s, +s, c) * nose; head[2] = mat2(c, +s, -s, c) * nose; @@ -76,8 +76,10 @@ class BaseDecorator(): #define CIRCLE_SEGS 24 // max points = SEGS (circle) + 2 (stem) + 3 (arrow) #define MAX_POINTS 29 - #define ARROW_ANGLE PI / 12.0 - #define ARROW_SIZE 16.0 + #define ARROW1_ANGLE PI / 12.0 + #define ARROW2_ANGLE PI / 3.0 + #define ARROW1_SIZE 16.0 + #define ARROW2_SIZE 24.0 #define CIRCLE_SIZE 8.0 #define DASH_SIZE 16.0 #define DASH_RATIO 0.5 @@ -244,7 +246,7 @@ class DimensionDecorator(BaseDecorator): vec2 head[3]; vec4 head_ndc[3]; - arrow_head(p0w, p1w, head); + arrow_head(p0w, p1w, ARROW1_SIZE, ARROW1_ANGLE, head); for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; vec4 gap = head_ndc[0]; @@ -384,7 +386,7 @@ class LeaderDecorator(BaseDecorator): vec2 head[3]; vec4 head_ndc[3]; - arrow_head(p0w, p1w, head); + arrow_head(p0w, p1w, ARROW1_SIZE, ARROW1_ANGLE, head); for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; // end edge arrow for last segment @@ -399,7 +401,7 @@ class LeaderDecorator(BaseDecorator): EmitVertex(); EndPrimitive(); - vec2 gap = normalize(p1w - p0w) * ARROW_SIZE; + vec2 gap = normalize(p1w - p0w) * ARROW1_SIZE; gap1 = unwindowMatrix * vec4(gap, 0, 0) * p1.w; } @@ -442,7 +444,7 @@ class StairDecorator(BaseDecorator): vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; - vec4 gap0 = vec4(0), gap1 = vec4(0); + vec4 gap0 = vec4(0); // start edge circle for first segment if (t0 == 1u) { @@ -464,27 +466,23 @@ class StairDecorator(BaseDecorator): if (t1 == 2u) { vec2 head[3]; vec4 head_ndc[3]; - arrow_head(p0w, p1w, head); + arrow_head(p0w, p1w, ARROW2_SIZE, ARROW2_ANGLE, head); for(int i=0; i<3; i++) head_ndc[i] = unwindowMatrix * vec4(head[i], 0, 0) * p1.w; - gl_Position = p1; - EmitVertex(); gl_Position = p1 - head_ndc[1]; EmitVertex(); + gl_Position = p1; + EmitVertex(); gl_Position = p1 - head_ndc[2]; EmitVertex(); - gl_Position = p1; - EmitVertex(); EndPrimitive(); - vec2 gap = normalize(p1w - p0w) * ARROW_SIZE; - gap1 = unwindowMatrix * vec4(gap, 0, 0) * p1.w; } // stem, adjusted for edge decoration gl_Position = p0 + gap0; EmitVertex(); - gl_Position = p1 - gap1; + gl_Position = p1; EmitVertex(); EndPrimitive(); } From 30f8350156b128d59594a7bc7ccfc0b6132e1129 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 22 Dec 2020 22:19:48 +0700 Subject: [PATCH 29/82] decorators for Levels --- .../blenderbim/bim/decoration.py | 297 ++++++++++++++---- src/ifcblenderexport/blenderbim/bim/prop.py | 4 + 2 files changed, 246 insertions(+), 55 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 30c644008b..6f16e65c7e 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -62,13 +62,33 @@ class BaseDecorator(): head[2] = mat2(c, +s, -s, c) * nose; } - void circle_head(in vec2 p0, in vec2 p1, out vec2 head[CIRCLE_SEGS]) { + void circle_head(in vec2 p0, in vec2 p1, in float size, out vec2 head[CIRCLE_SEGS]) { float angle_d = PI * 2 / CIRCLE_SEGS; for(int i = 0; i DASH_RATIO) { + float t = fract(dist / DASH1_SIZE); + if (t > 0.5) { discard; } else { fragColor = vec4(1.0, 1.0, 1.0, 1.0); @@ -548,3 +570,168 @@ class HiddenDecorator(BaseDecorator): else: verts, idxs = self.get_mesh_geom(obj) self.draw_lines(obj, verts, idxs) + + +class LevelDecorator(BaseDecorator): + + def get_splines(self, obj): + """Iterates through splines + Args: + obj: Blender object with Curve data + + Yields: + verts: points of each spline, world coords + """ + for spline in obj.data.splines: + spline_points = spline.bezier_points if spline.bezier_points else spline.points + yield [obj.matrix_world @ p.co for p in spline_points] + + def decorate(self, obj): + verts, idxs, topo = self.get_path_geom(obj) + self.draw_lines(obj, verts, idxs, topo) + splines = self.get_splines(obj) + self.draw_labels(obj, splines) + + +class PlanDecorator(LevelDecorator): + basename = "IfcAnnotation/Plan Level" + installed = None + + GEOM_GLSL = """ + uniform vec2 winsize; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + in uint type[]; + + void main() { + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + uint t0 = type[0], t1 = type[1]; + + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; + + vec4 gap1 = vec4(0); + + // end edge cross+circle for last segment + if (t1 == 2u) { + vec2 head_o[CIRCLE_SEGS]; + + circle_head(p0w, p1w, CIRCLE2_SIZE, head_o); + for(int i=0; i 0.875 || (t > 0.25 && t < 0.375)) { + discard; + } else { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + } + """ + + def draw_labels(self, obj, splines): + region = self.context.region + region3d = self.context.region_data + for verts in splines: + v0 = verts[0] + v1 = verts[1] + p0 = location_3d_to_region_2d(region, region3d, v0) + p1 = location_3d_to_region_2d(region, region3d, v1) + dir = p1 - p0 + elev = verts[-1].z + text = f"RL {elev:.2f}" + self.draw_label(text, p0 + dir.normalized() * 16, -dir, gap=16, center=False) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 4b83aa25bd..09a7d8e10f 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -366,12 +366,16 @@ def toggleDecorations(self, context): decoration.LeaderDecorator.install(self, context) decoration.StairDecorator.install(self, context) decoration.HiddenDecorator.install(self, context) + decoration.PlanDecorator.install(self, context) + decoration.SectionDecorator.install(self, context) else: decoration.DimensionDecorator.uninstall() decoration.EqualityDecorator.uninstall() decoration.LeaderDecorator.uninstall() decoration.StairDecorator.uninstall() decoration.HiddenDecorator.uninstall() + decoration.PlanDecorator.uninstall() + decoration.SectionDecorator.uninstall() def getScenarios(self, context): From 6dfeaf55097f743b4644590db2d8174e676a9b86 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 22 Dec 2020 22:46:49 +0700 Subject: [PATCH 30/82] bpy-based formatting units for decoration close #1164 --- .../blenderbim/bim/decoration.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 6f16e65c7e..d8d478c26a 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -281,6 +281,23 @@ class BaseDecorator(): blf.draw(self.font_id, text) blf.disable(self.font_id, blf.ROTATION) + def format_value(self, value): + unit_system = bpy.context.scene.unit_settings.system + if unit_system == 'IMPERIAL': + precision = bpy.context.scene.BIMProperties.imperial_precision + if precision == "NONE": + precision = 256 + elif precision == "1": + precision = 1 + elif "/" in precision: + precision = int(precision.split("/")[1]) + elif unit_system == 'METRIC': + precision = 3 + else: + return + + return bpy.utils.units.to_string(unit_system, 'LENGTH', value, precision, split_unit=True) + class DimensionDecorator(BaseDecorator): """Decorator for dimension objects @@ -352,7 +369,7 @@ class DimensionDecorator(BaseDecorator): v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) length = (v1 - v0).length - text = f"{length:.2f}" + text = self.format_value(length) p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 @@ -658,8 +675,7 @@ class PlanDecorator(LevelDecorator): p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 - elev = verts[-1].z - text = f"RL {elev:.2f}" + text = "RL " + self.format_value(verts[-1].z) self.draw_label(text, p0, dir, gap=8, center=False) @@ -732,6 +748,5 @@ class SectionDecorator(LevelDecorator): p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 - elev = verts[-1].z - text = f"RL {elev:.2f}" + text = "RL " + self.format_value(verts[-1].z) self.draw_label(text, p0 + dir.normalized() * 16, -dir, gap=16, center=False) From 4df8418e72c83da3548c343c1d554f47370d4545 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Tue, 22 Dec 2020 14:34:04 +0100 Subject: [PATCH 31/82] bimtester: steps, add more attributes tests --- .../features/steps/attributes_eleclasses.py | 15 ++++++ .../steps/attributes_eleclasses_methods.py | 50 +++++++++++++++++++ .../features/steps/attributes_psets.py | 9 ++++ .../steps/attributes_psets_methods.py | 34 +++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/ifcbimtester/bimtester/features/steps/attributes_psets_methods.py diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py index d63ca60d2f..b3b6b7d236 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses.py @@ -1,7 +1,10 @@ from behave import step from attributes_eleclasses_methods import all_element_attribs_have_a_value +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 IfcFile from utils import switch_locale @@ -18,6 +21,18 @@ def step_impl(context, ifc_class): all_element_attribs_have_a_value(context, ifc_class) +@step('all {ifc_class} elements have a name given') +def step_impl(context, ifc_class): + switch_locale(context.localedir, "en") + name_has_a_value(context, ifc_class) + + +@step('all {ifc_class} elements have a description given') +def step_impl(context, ifc_class): + switch_locale(context.localedir, "en") + description_has_a_value(context, ifc_class) + + @step('all {ifc_class} elements have a name matching the pattern "{pattern}"') def step_impl(context, ifc_class, pattern): import re diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py index 03cfd5f437..0588bd3d2f 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_eleclasses_methods.py @@ -82,3 +82,53 @@ def all_element_attribs_have_a_value(context, ifc_class): message_no_elems=_("There are no {ifc_class} elements in the IFC file."), parameter=failed_attribs ) + + +def name_has_a_value(context, ifc_class): + + context.falseelems = [] + context.falseguids = [] + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + # print(elem.Name) + if not elem.Name: + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + assert_elements( + ifc_class, + context.elemcount, + context.falsecount, + context.falseelems, + message_all_falseelems=_("The name of all {elemcount} {elemcount} elements is not set."), + message_some_falseelems=_("The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: {falseelems}"), + message_no_elems=_("There are no {ifc_class} elements in the IFC file."), + ) + + +def description_has_a_value(context, ifc_class): + + context.falseelems = [] + context.falseguids = [] + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + # print(elem.Description) + if not elem.Description: + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + assert_elements( + ifc_class, + context.elemcount, + context.falsecount, + context.falseelems, + message_all_falseelems=_("The description of all {elemcount} {elemcount} elements is not set."), + message_some_falseelems=_("The description of {falsecount} out of {elemcount} {ifc_class} elements is not set: {falseelems}"), + message_no_elems=_("There are no {ifc_class} elements in the IFC file."), + ) diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_psets.py b/src/ifcbimtester/bimtester/features/steps/attributes_psets.py index 9d46a6cc14..124c54467a 100644 --- a/src/ifcbimtester/bimtester/features/steps/attributes_psets.py +++ b/src/ifcbimtester/bimtester/features/steps/attributes_psets.py @@ -1,6 +1,15 @@ from behave import step +from attributes_psets_methods import all_eleclass_elements_have_prop_in_pset +from utils import assert_elements from utils import IfcFile +from utils import switch_locale + + +@step("all {ifc_class} elements have an {aproperty} property in the {pset} pset") +def step_impl(context, ifc_class, aproperty, pset): + switch_locale(context.localedir, "en") + all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset) # ------------------------------------------------------------------------ diff --git a/src/ifcbimtester/bimtester/features/steps/attributes_psets_methods.py b/src/ifcbimtester/bimtester/features/steps/attributes_psets_methods.py new file mode 100644 index 0000000000..61c707b6da --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/attributes_psets_methods.py @@ -0,0 +1,34 @@ +import gettext # noqa + +from utils import assert_elements +from utils import IfcFile + + +def all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset): + + context.falseelems = [] + context.falseguids = [] + context.falseprops = {} + from ifcopenshell.util.element import get_psets + + elements = IfcFile.get().by_type(ifc_class) + for elem in elements: + psets = get_psets(elem) + if not (pset in psets and aproperty in psets[pset]): + context.falseelems.append(str(elem)) + context.falseguids.append(elem.GlobalId) + context.falseprops[elem.id()] = str(psets) + + context.elemcount = len(elements) + context.falsecount = len(context.falseelems) + assert_elements( + ifc_class, + context.elemcount, + context.falsecount, + context.falseelems, + message_all_falseelems=_("All {elemcount} {ifc_class} elements are missing the property {parameter} in the pset."), + message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are missing the property {parameter} in the pset: {falseelems}"), + message_no_elems=_("There are no {ifc_class} elements in the IFC file."), + parameter=aproperty + ) + # the pset name is missing in the failing message, but it is in the step test name From 27051cce8ae512ee5e5c5631113ec689bb0a2f13 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Tue, 22 Dec 2020 20:28:40 +0100 Subject: [PATCH 32/82] bimtester: move report data into new directory --- .../bimtester/{features => reportdata}/strings_template_de.json | 0 .../bimtester/{features => reportdata}/strings_template_en.json | 0 .../bimtester/{features => reportdata}/strings_template_fr.json | 0 .../bimtester/{features => reportdata}/template.html | 0 src/ifcbimtester/bimtester/reports.py | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename src/ifcbimtester/bimtester/{features => reportdata}/strings_template_de.json (100%) rename src/ifcbimtester/bimtester/{features => reportdata}/strings_template_en.json (100%) rename src/ifcbimtester/bimtester/{features => reportdata}/strings_template_fr.json (100%) rename src/ifcbimtester/bimtester/{features => reportdata}/template.html (100%) diff --git a/src/ifcbimtester/bimtester/features/strings_template_de.json b/src/ifcbimtester/bimtester/reportdata/strings_template_de.json similarity index 100% rename from src/ifcbimtester/bimtester/features/strings_template_de.json rename to src/ifcbimtester/bimtester/reportdata/strings_template_de.json diff --git a/src/ifcbimtester/bimtester/features/strings_template_en.json b/src/ifcbimtester/bimtester/reportdata/strings_template_en.json similarity index 100% rename from src/ifcbimtester/bimtester/features/strings_template_en.json rename to src/ifcbimtester/bimtester/reportdata/strings_template_en.json diff --git a/src/ifcbimtester/bimtester/features/strings_template_fr.json b/src/ifcbimtester/bimtester/reportdata/strings_template_fr.json similarity index 100% rename from src/ifcbimtester/bimtester/features/strings_template_fr.json rename to src/ifcbimtester/bimtester/reportdata/strings_template_fr.json diff --git a/src/ifcbimtester/bimtester/features/template.html b/src/ifcbimtester/bimtester/reportdata/template.html similarity index 100% rename from src/ifcbimtester/bimtester/features/template.html rename to src/ifcbimtester/bimtester/reportdata/template.html diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index 180cf7147e..0828e9661b 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -10,7 +10,7 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_ # get html template path report_template_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), - "features/" + "reportdata/" ) if html_template_file_path: From b22055a588f93e35aeb66bc742e74e47ce330781 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Tue, 22 Dec 2020 20:35:44 +0100 Subject: [PATCH 33/82] bimtester: once again move report data into resources dir --- src/ifcbimtester/bimtester/reports.py | 3 ++- .../{reportdata => resources/reports}/strings_template_de.json | 0 .../{reportdata => resources/reports}/strings_template_en.json | 0 .../{reportdata => resources/reports}/strings_template_fr.json | 0 .../bimtester/{reportdata => resources/reports}/template.html | 0 5 files changed, 2 insertions(+), 1 deletion(-) rename src/ifcbimtester/bimtester/{reportdata => resources/reports}/strings_template_de.json (100%) rename src/ifcbimtester/bimtester/{reportdata => resources/reports}/strings_template_en.json (100%) rename src/ifcbimtester/bimtester/{reportdata => resources/reports}/strings_template_fr.json (100%) rename src/ifcbimtester/bimtester/{reportdata => resources/reports}/template.html (100%) diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index 0828e9661b..2d7e1eb05c 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -10,7 +10,8 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_ # get html template path report_template_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), - "reportdata/" + "resources", + "reports" ) if html_template_file_path: diff --git a/src/ifcbimtester/bimtester/reportdata/strings_template_de.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_de.json similarity index 100% rename from src/ifcbimtester/bimtester/reportdata/strings_template_de.json rename to src/ifcbimtester/bimtester/resources/reports/strings_template_de.json diff --git a/src/ifcbimtester/bimtester/reportdata/strings_template_en.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_en.json similarity index 100% rename from src/ifcbimtester/bimtester/reportdata/strings_template_en.json rename to src/ifcbimtester/bimtester/resources/reports/strings_template_en.json diff --git a/src/ifcbimtester/bimtester/reportdata/strings_template_fr.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json similarity index 100% rename from src/ifcbimtester/bimtester/reportdata/strings_template_fr.json rename to src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json diff --git a/src/ifcbimtester/bimtester/reportdata/template.html b/src/ifcbimtester/bimtester/resources/reports/template.html similarity index 100% rename from src/ifcbimtester/bimtester/reportdata/template.html rename to src/ifcbimtester/bimtester/resources/reports/template.html From 5a1f2ec4fe07bd60f864be2d151330910bf2d0ae Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 23 Dec 2020 13:58:28 +1100 Subject: [PATCH 34/82] Implement support for editing BCF comments --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 18 +++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 7 ++++- src/ifcblenderexport/blenderbim/bim/ui.py | 27 +++++++++++-------- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 5a956f6159..5ca15263a8 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -38,6 +38,7 @@ if bpy is not None: operator.ViewBcfTopic, operator.RemoveBcfViewpoint, operator.RemoveBcfComment, + operator.EditBcfComment, operator.AddBcfComment, operator.ActivateBcfViewpoint, operator.OpenUri, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 1f11523145..60efc7ccc2 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -771,6 +771,24 @@ class RemoveBcfComment(bpy.types.Operator): return {"FINISHED"} +class EditBcfComment(bpy.types.Operator): + bl_idname = "bim.edit_bcf_comment" + bl_label = "Edit BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + blender_comment = blender_topic.comments.get(self.comment_guid) + topic = bcfxml.topics[blender_topic.name] + comment = topic.comments[self.comment_guid] + comment.comment = blender_comment.comment + bcfxml.edit_comment(comment, topic) + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + return {"FINISHED"} + + class AddBcfComment(bpy.types.Operator): bl_idname = "bim.add_bcf_comment" bl_label = "Add BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 09a7d8e10f..e0f8ecf986 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -566,10 +566,14 @@ def updateBcfTopicName(self, context): def updateBcfTopicIsEditable(self, context): if not self.is_editable: - print("EDITING!") bpy.ops.bim.edit_bcf_topic() +def updateBcfCommentIsEditable(self, context): + if not self.is_editable: + bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) + + def refreshBcfTopic(self, context): global bcfviewpoints_enum bcfviewpoints_enum = None @@ -985,6 +989,7 @@ class BcfComment(PropertyGroup): viewpoint: StringProperty(name="Viewpoint") modified_date: StringProperty(name="Modified Date") modified_author: StringProperty(name="Modified Author") + is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable) class BcfTopic(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 43fdf91170..010a47678f 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1843,19 +1843,24 @@ class BIM_PT_bcf_comments(Panel): author_text = "*{} ({})".format(comment.modified_author, comment.modified_date) row = box.row(align=True) row.label(text=author_text, icon="WORDWRAP_ON") + row.prop(comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True) row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name - col = box.column(align=True) - col.scale_y = 0.8 - words = comment.comment.split() - while words: - total_line_chars = 0 - line_words = [] - while words and total_line_chars < props.comment_text_width: - word = words.pop(0) - line_words.append(word) - total_line_chars += len(word) + 1 # 1 is for the space - col.label(text=" ".join(line_words)) + if comment.is_editable: + row = box.row() + row.prop(comment, "comment", text="") + else: + col = box.column(align=True) + col.scale_y = 0.8 + words = comment.comment.split() + while words: + total_line_chars = 0 + line_words = [] + while words and total_line_chars < props.comment_text_width: + word = words.pop(0) + line_words.append(word) + total_line_chars += len(word) + 1 # 1 is for the space + col.label(text=" ".join(line_words)) row = layout.row() row.prop(topic, "comment") From 4d2be3e3c5792093c369e0674535a0bf7307abdf Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 23 Dec 2020 15:53:18 +1100 Subject: [PATCH 35/82] Since current implementations will allow BCF FoV outside the 45-60 range, let's allow it --- src/bcf/bcf/xsd/visinfo.xsd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bcf/bcf/xsd/visinfo.xsd b/src/bcf/bcf/xsd/visinfo.xsd index 8bec85b445..c54cbf8dd5 100644 --- a/src/bcf/bcf/xsd/visinfo.xsd +++ b/src/bcf/bcf/xsd/visinfo.xsd @@ -91,8 +91,8 @@ - - + + From 7b2fa3e877113fc7b3edcf9acbd9a85a36efac25 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 23 Dec 2020 15:53:38 +1100 Subject: [PATCH 36/82] Add support for adding BCF viewpoints --- src/bcf/bcf/bcfxml.py | 6 ++- .../blenderbim/bim/__init__.py | 3 +- .../blenderbim/bim/operator.py | 51 ++++++++++++++++++- src/ifcblenderexport/blenderbim/bim/ui.py | 1 + 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index c1291283f7..e570dc0684 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -9,6 +9,7 @@ from datetime import datetime from xml.dom import minidom from xmlschema import XMLSchema from contextlib import contextmanager +from shutil import copyfile cwd = os.path.dirname(os.path.realpath(__file__)) @@ -476,7 +477,10 @@ class BcfXml: topic_filepath = os.path.join(self.filepath, topic.guid) filepath = os.path.join(topic_filepath, viewpoint.snapshot) if not os.path.exists(filepath): - pass # TODO: handle uploading files + filename = viewpoint.guid + "." + viewpoint.snapshot[-3:] + copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename)) + viewpoint.snapshot = filename + topic.viewpoints[viewpoint.guid] = viewpoint self.edit_topic(topic) def delete_viewpoint(self, guid, topic): diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 5ca15263a8..d31ed30e45 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -36,11 +36,12 @@ if bpy is not None: operator.SaveBcfProject, operator.AddBcfTopic, operator.ViewBcfTopic, - operator.RemoveBcfViewpoint, operator.RemoveBcfComment, operator.EditBcfComment, operator.AddBcfComment, operator.ActivateBcfViewpoint, + operator.AddBcfViewpoint, + operator.RemoveBcfViewpoint, operator.OpenUri, operator.OpenBcfReferenceLink, operator.SelectFeaturesDir, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 60efc7ccc2..f58c9b2e58 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -28,7 +28,7 @@ from . import helper from bpy_extras.io_utils import ImportHelper from itertools import cycle from mathutils import Vector, Matrix, Euler, geometry -from math import radians, atan, tan, cos, sin +from math import radians, degrees, atan, tan, cos, sin from pathlib import Path from bpy.app.handlers import persistent @@ -741,6 +741,53 @@ class ViewBcfTopic(bpy.types.Operator): return {"FINISHED"} +class AddBcfViewpoint(bpy.types.Operator): + bl_idname = "bim.add_bcf_viewpoint" + bl_label = "Add BCF Viewpoint" + + def execute(self, context): + if not bpy.context.scene.camera: + return {"FINISHED"} + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + viewpoint = bcf.data.Viewpoint() + + if bpy.context.scene.camera.data.type == "ORTHO": + camera = bcf.data.OrthogonalCamera() + camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale + viewpoint.orthogonal_camera = camera + elif bpy.context.scene.camera.data.type == "PERSP": + camera = bcf.data.PerspectiveCamera() + camera.field_of_view = degrees(bpy.context.scene.camera.data.angle) + viewpoint.perspective_camera = camera + camera.camera_view_point.x = bpy.context.scene.camera.location.x + camera.camera_view_point.y = bpy.context.scene.camera.location.y + camera.camera_view_point.z = bpy.context.scene.camera.location.z + direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) + camera.camera_direction.x = direction.x + camera.camera_direction.y = direction.y + camera.camera_direction.z = direction.z + up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) + camera.camera_up_vector.x = up.x + camera.camera_up_vector.y = up.y + camera.camera_up_vector.z = up.z + + old_file_format = bpy.context.scene.render.image_settings.file_format + bpy.context.scene.render.image_settings.file_format = "PNG" + old_filepath = bpy.context.scene.render.filepath + bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png") + bpy.ops.render.opengl(write_still=True) + viewpoint.snapshot = bpy.context.scene.render.filepath + + bcfxml.add_viewpoint(topic, viewpoint) + bpy.context.scene.render.filepath = old_filepath + bpy.context.scene.render.image_settings.file_format = old_file_format + props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + return {"FINISHED"} + + class RemoveBcfViewpoint(bpy.types.Operator): bl_idname = "bim.remove_bcf_viewpoint" bl_label = "Remove BCF Viewpoint" @@ -896,6 +943,8 @@ class ActivateBcfViewpoint(bpy.types.Operator): return {"FINISHED"} def set_viewpoint_components(self, viewpoint): + if not viewpoint.components: + return selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection] exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions] global_id_colours = {} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 010a47678f..7d341a2d8b 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1696,6 +1696,7 @@ class BIM_PT_bcf(Panel): row = layout.row() row.prop(topic, "viewpoints") row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") + row.operator("bim.add_bcf_viewpoint", icon="ADD", text="") row.operator("bim.remove_bcf_viewpoint", icon="X", text="") col = layout.column(align=True) From 67f7e68fa7c6c13a079c0116a09385589163671f Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 23 Dec 2020 16:22:29 +1100 Subject: [PATCH 37/82] Implement support for deleting BCF header files --- src/bcf/bcf/bcfxml.py | 11 ++++++++++- src/ifcblenderexport/blenderbim/bim/__init__.py | 1 + src/ifcblenderexport/blenderbim/bim/operator.py | 15 +++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 3 ++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index e570dc0684..6d57f8975b 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -272,7 +272,7 @@ class BcfXml: f.write(self.document.toprettyxml(encoding="utf-8")) def write_header(self, header, root): - if not header: + if not header or not header.files: return header_el = self._create_element(root, "Header") for f in header.files: @@ -504,6 +504,15 @@ class BcfXml: del topic.viewpoints[guid] self.edit_topic(topic) + def delete_file(self, topic, index): + if not topic.header: + return + f = topic.header.files.pop(index) + filepath = os.path.join(self.filepath, topic.guid, f.reference) + if not f.is_external and os.path.exists(filepath): + os.remove(filepath) + self.edit_topic(topic) + def get_comments(self, guid): comments = {} data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd") diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index d31ed30e45..62eb4030aa 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -42,6 +42,7 @@ if bpy is not None: operator.ActivateBcfViewpoint, operator.AddBcfViewpoint, operator.RemoveBcfViewpoint, + operator.RemoveBcfFile, operator.OpenUri, operator.OpenBcfReferenceLink, operator.SelectFeaturesDir, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f58c9b2e58..ad1ed48e43 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -803,6 +803,21 @@ class RemoveBcfViewpoint(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfFile(bpy.types.Operator): + bl_idname = "bim.remove_bcf_file" + bl_label = "Remove BCF File" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_file(topic, self.index) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 7d341a2d8b..c8ea3dc1ea 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1747,7 +1747,7 @@ class BIM_PT_bcf_metadata(Panel): bcfxml = bcfstore.BcfStore.get_bcfxml() bcf_topic = bcfxml.topics[topic.name] - if bcf_topic.header: + if bcf_topic.header and bcf_topic.header.files: layout.label(text="Header Files:") for index, f in enumerate(bcf_topic.header.files): box = self.layout.box() @@ -1758,6 +1758,7 @@ class BIM_PT_bcf_metadata(Panel): else: op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) + row.operator("bim.remove_bcf_file", icon="X", text="").index = index box.label(text=f.date) # box.label(text=f.ifc_project) # box.label(text=f.ifc_spatial_structure_element) From 666641bb18d4dfae5187f3b6921ec8db57f5ed46 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 23 Dec 2020 11:33:33 +0100 Subject: [PATCH 38/82] bimtester: reports, code formating --- src/ifcbimtester/bimtester/reports.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index 2d7e1eb05c..201e9d12f9 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -4,8 +4,14 @@ import os import pystache -def generate_report(adir=".", use_report_folder=True, report_file_name="", html_template_file_path=""): - #print("# Generating HTML reports now.") +def generate_report( + adir=".", + use_report_folder=True, + report_file_name="", + html_template_file_path="" +): + + # print("# Generating HTML reports now.") # get html template path report_template_path = os.path.join( @@ -25,9 +31,9 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_ return print("No report directory was found.") if report_file_name: - report_path = os.path.join(report_dir, report_file_name) + report_path = os.path.join(report_dir, report_file_name) else: - report_path = os.path.join(report_dir, "report.json") + report_path = os.path.join(report_dir, "report.json") # print(report_path) if not os.path.exists(report_path): return print("No report data was found.") From 1b6857b8baea304a840f0851a6df23d7bfcd2467 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 23 Dec 2020 11:31:06 +0100 Subject: [PATCH 39/82] bimtester: also use gettext for html report translation --- .../locale/de/LC_MESSAGES/messages.po | 163 +++++++++++++++++- .../locale/en/LC_MESSAGES/messages.po | 154 ++++++++++++++++- .../locale/fr/LC_MESSAGES/messages.po | 117 ++++++++++++- .../bimtester/locale/messages.pot | 117 ++++++++++++- src/ifcbimtester/bimtester/reports.py | 46 +++-- .../reports/strings_template_de.json | 9 - .../reports/strings_template_en.json | 9 - .../reports/strings_template_fr.json | 9 - 8 files changed, 568 insertions(+), 56 deletions(-) delete mode 100644 src/ifcbimtester/bimtester/resources/reports/strings_template_de.json delete mode 100644 src/ifcbimtester/bimtester/resources/reports/strings_template_en.json delete mode 100644 src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json diff --git a/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po index a1a86662aa..232eaf60eb 100644 --- a/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po +++ b/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-12-17 15:14+0100\n" -"PO-Revision-Date: 2020-12-18 06:11+0100\n" +"POT-Creation-Date: 2020-12-23 11:03+0100\n" +"PO-Revision-Date: 2020-12-23 11:20+0100\n" "Last-Translator: \n" "Language: de\n" "Language-Team: de \n" @@ -19,6 +19,165 @@ msgstr "" "Generated-By: Babel 2.6.0\n" "X-Generator: Poedit 2.2.1\n" +#: reports.py:153 reports.py:160 +msgid "OpenBIM auditing is a feature of" +msgstr "OpenBIM auditing ist eine Funktionalität von" + +#: reports.py:155 +msgid "en" +msgstr "de" + +#: reports.py:156 +msgid "Success" +msgstr "Bestanden" + +#: reports.py:157 +msgid "Failure" +msgstr "Durchgefallen" + +#: reports.py:158 +msgid "Tests passed" +msgstr "Erfolgreiche Tests" + +#: reports.py:159 +msgid "Duration" +msgstr "Dauer" + +#: reports.py:161 +msgid "and" +msgstr "und" + +#: features/steps/attributes_eleclasses_methods.py:24 +msgid "All {elemcount} elements in the file are {ifc_class}." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:32 +msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158 +msgid "Error in falsecount, something went wrong." +msgstr "Fehler in falsecount, es ist etwas falsch gelaufen." + +#: features/steps/attributes_eleclasses_methods.py:80 +msgid "" +"For all {elemcount} {ifc_class} elements at least one of these class attributes " +"{parameter} has no value." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:81 +msgid "" +"For the following {falsecount} out of {elemcount} {ifc_class} elements at least " +"one of these class attributes {parameter} has no value: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:82 +#: features/steps/attributes_eleclasses_methods.py:108 +#: features/steps/attributes_eleclasses_methods.py:133 +#: features/steps/attributes_psets_methods.py:31 +#: features/steps/geometric_detail_methods.py:52 +#: features/steps/geometric_detail_methods.py:127 +msgid "There are no {ifc_class} elements in the IFC file." +msgstr "Es sind keine {ifc_class} Bauteile in der IFC-Datei." + +#: features/steps/attributes_eleclasses_methods.py:106 +msgid "The name of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:107 +msgid "" +"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: " +"{falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:131 +msgid "The description of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:132 +msgid "" +"The description of {falsecount} out of {elemcount} {ifc_class} elements is not " +"set: {falseelems}" +msgstr "" + +#: features/steps/attributes_psets_methods.py:29 +msgid "" +"All {elemcount} {ifc_class} elements are missing the property {parameter} in " +"the pset." +msgstr "" + +#: features/steps/attributes_psets_methods.py:30 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are missing the " +"property {parameter} in the pset: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:50 +msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation." +msgstr "" +"Alle {elemcount} {ifc_class} Bauteile haben keine geometrische Repräsentation " +"der Klasse {parameter}." + +#: features/steps/geometric_detail_methods.py:51 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are not a " +"{parameter} representation: {falseelems}" +msgstr "" +"Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen " +"haben keine geometrische Repräsentation der Klasse {parameter}: {falseelems}" + +#: features/steps/geometric_detail_methods.py:125 +msgid "The geometry of all {elemcount} {ifc_class} elements have errors." +msgstr "" +"Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} Bauteilen " +"haben Fehler." + +#: features/steps/geometric_detail_methods.py:126 +msgid "" +"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have " +"errors: {falseelems}" +msgstr "" +"Die geometrischen Repräsentationen von {falsecount} von allen {elemcount} " +"{ifc_class} Bauteilen haben Fehler: {falseelems}" + #: features/steps/ifcdata_methods.py:7 msgid "We expected a schema of {} but instead got {}" msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}" + +#~ msgid "The geometry of all {} {} elements have errors." +#~ msgstr "" +#~ "Die geometrischen Repräsentationen von allen {} {} Bauteilen haben Fehler." + +#~ msgid "The geometry of {} out of all {} {} elements have errors: {}" +#~ msgstr "" +#~ "Die geometrischen Repräsentationen von {} von allen {} {} Bauteilen haben " +#~ "Fehler: {}" + +#~ msgid "There are no {} elements in the IFC file." +#~ msgstr "Es sind keine {} Bauteile in der IFC-Datei." + +#~ msgid "All {} {} elements are not a IfcFacetedBrep representation." +#~ msgstr "" +#~ "Alle {} {} Bauteile haben keine geometrische Repräsentation der Klasse " +#~ "IfcFacetedBrep." + +#~ msgid "" +#~ "The following {} of {} {} elements are not a IfcFacetedBrep representation: " +#~ "{}" +#~ msgstr "" +#~ "Die Anzahl Bauteile {} von allen {} {} Bauteilen haben keine geometrische " +#~ "Repräsentation der Klasse IfcFacetedBrep: {}" + +#~ msgid "" +#~ "All {elemcount} {ifc_class} elements are not a IfcFacetedBrep representation." +#~ msgstr "" +#~ "Alle {elemcount} {ifc_class} Bauteile haben keine geometrische " +#~ "Repräsentation der Klasse IfcFacetedBrep." + +#~ msgid "" +#~ "The following {falsecount} of {elemcount} {ifc_class} elements are not a " +#~ "IfcFacetedBrep representation: {falseelems}" +#~ msgstr "" +#~ "Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen " +#~ "haben keine geometrische Repräsentation der Klasse IfcFacetedBrep: " +#~ "{falseelems}" diff --git a/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po index 86be615289..bd6ad28f0b 100644 --- a/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po +++ b/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po @@ -7,18 +7,162 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-12-17 15:14+0100\n" -"PO-Revision-Date: 2020-12-18 06:12+0100\n" +"POT-Creation-Date: 2020-12-23 11:03+0100\n" +"PO-Revision-Date: 2020-12-18 11:41+0100\n" "Last-Translator: \n" "Language: en\n" "Language-Team: en \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" -"X-Generator: Poedit 2.2.1\n" + +#: reports.py:153 reports.py:160 +msgid "OpenBIM auditing is a feature of" +msgstr "" + +#: reports.py:155 +msgid "en" +msgstr "" + +#: reports.py:156 +msgid "Success" +msgstr "" + +#: reports.py:157 +msgid "Failure" +msgstr "" + +#: reports.py:158 +msgid "Tests passed" +msgstr "" + +#: reports.py:159 +msgid "Duration" +msgstr "" + +#: reports.py:161 +msgid "and" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:24 +msgid "All {elemcount} elements in the file are {ifc_class}." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:32 +msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:41 +#: features/steps/utils.py:158 +msgid "Error in falsecount, something went wrong." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:80 +msgid "" +"For all {elemcount} {ifc_class} elements at least one of these class " +"attributes {parameter} has no value." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:81 +msgid "" +"For the following {falsecount} out of {elemcount} {ifc_class} elements at" +" least one of these class attributes {parameter} has no value: " +"{falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:82 +#: features/steps/attributes_eleclasses_methods.py:108 +#: features/steps/attributes_eleclasses_methods.py:133 +#: features/steps/attributes_psets_methods.py:31 +#: features/steps/geometric_detail_methods.py:52 +#: features/steps/geometric_detail_methods.py:127 +msgid "There are no {ifc_class} elements in the IFC file." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:106 +msgid "The name of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:107 +msgid "" +"The name of {falsecount} out of {elemcount} {ifc_class} elements is not " +"set: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:131 +msgid "The description of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:132 +msgid "" +"The description of {falsecount} out of {elemcount} {ifc_class} elements " +"is not set: {falseelems}" +msgstr "" + +#: features/steps/attributes_psets_methods.py:29 +msgid "" +"All {elemcount} {ifc_class} elements are missing the property {parameter}" +" in the pset." +msgstr "" + +#: features/steps/attributes_psets_methods.py:30 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are " +"missing the property {parameter} in the pset: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:50 +msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation." +msgstr "" + +#: features/steps/geometric_detail_methods.py:51 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are not a " +"{parameter} representation: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:125 +msgid "The geometry of all {elemcount} {ifc_class} elements have errors." +msgstr "" + +#: features/steps/geometric_detail_methods.py:126 +msgid "" +"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements " +"have errors: {falseelems}" +msgstr "" #: features/steps/ifcdata_methods.py:7 msgid "We expected a schema of {} but instead got {}" -msgstr "We expected a schema of {} but instead got {}" +msgstr "" + +#~ msgid "The geometry of all {} {} elements have errors." +#~ msgstr "" + +#~ msgid "The geometry of {} out of all {} {} elements have errors: {}" +#~ msgstr "" + +#~ msgid "There are no {} elements in the IFC file." +#~ msgstr "" + +#~ msgid "All {} {} elements are not a IfcFacetedBrep representation." +#~ msgstr "" + +#~ msgid "" +#~ "The following {} of {} {} elements" +#~ " are not a IfcFacetedBrep representation:" +#~ " {}" +#~ msgstr "" + +#~ msgid "" +#~ "All {elemcount} {ifc_class} elements are " +#~ "not a IfcFacetedBrep representation." +#~ msgstr "" + +#~ msgid "" +#~ "The following {falsecount} of {elemcount} " +#~ "{ifc_class} elements are not a " +#~ "IfcFacetedBrep representation: {falseelems}" +#~ msgstr "" + diff --git a/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po index 0517d51973..cf837b8ebe 100644 --- a/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po +++ b/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-12-17 15:14+0100\n" -"PO-Revision-Date: 2020-12-18 06:12+0100\n" +"POT-Creation-Date: 2020-12-23 11:03+0100\n" +"PO-Revision-Date: 2020-12-23 11:21+0100\n" "Last-Translator: \n" "Language: fr\n" "Language-Team: fr \n" @@ -19,6 +19,119 @@ msgstr "" "Generated-By: Babel 2.6.0\n" "X-Generator: Poedit 2.2.1\n" +#: reports.py:153 reports.py:160 +msgid "OpenBIM auditing is a feature of" +msgstr "L'audit OpenBIM auditing est une fonctionnalité de" + +#: reports.py:155 +msgid "en" +msgstr "fr" + +#: reports.py:156 +msgid "Success" +msgstr "Succès" + +#: reports.py:157 +msgid "Failure" +msgstr "Échec" + +#: reports.py:158 +msgid "Tests passed" +msgstr "Tests réussis" + +#: reports.py:159 +msgid "Duration" +msgstr "Durée" + +#: reports.py:161 +msgid "and" +msgstr "et" + +#: features/steps/attributes_eleclasses_methods.py:24 +msgid "All {elemcount} elements in the file are {ifc_class}." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:32 +msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158 +msgid "Error in falsecount, something went wrong." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:80 +msgid "" +"For all {elemcount} {ifc_class} elements at least one of these class attributes " +"{parameter} has no value." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:81 +msgid "" +"For the following {falsecount} out of {elemcount} {ifc_class} elements at least " +"one of these class attributes {parameter} has no value: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:82 +#: features/steps/attributes_eleclasses_methods.py:108 +#: features/steps/attributes_eleclasses_methods.py:133 +#: features/steps/attributes_psets_methods.py:31 +#: features/steps/geometric_detail_methods.py:52 +#: features/steps/geometric_detail_methods.py:127 +msgid "There are no {ifc_class} elements in the IFC file." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:106 +msgid "The name of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:107 +msgid "" +"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: " +"{falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:131 +msgid "The description of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:132 +msgid "" +"The description of {falsecount} out of {elemcount} {ifc_class} elements is not " +"set: {falseelems}" +msgstr "" + +#: features/steps/attributes_psets_methods.py:29 +msgid "" +"All {elemcount} {ifc_class} elements are missing the property {parameter} in " +"the pset." +msgstr "" + +#: features/steps/attributes_psets_methods.py:30 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are missing the " +"property {parameter} in the pset: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:50 +msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation." +msgstr "" + +#: features/steps/geometric_detail_methods.py:51 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are not a " +"{parameter} representation: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:125 +msgid "The geometry of all {elemcount} {ifc_class} elements have errors." +msgstr "" + +#: features/steps/geometric_detail_methods.py:126 +msgid "" +"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have " +"errors: {falseelems}" +msgstr "" + #: features/steps/ifcdata_methods.py:7 msgid "We expected a schema of {} but instead got {}" msgstr "" diff --git a/src/ifcbimtester/bimtester/locale/messages.pot b/src/ifcbimtester/bimtester/locale/messages.pot index ac5ec26174..5d7a7d9221 100644 --- a/src/ifcbimtester/bimtester/locale/messages.pot +++ b/src/ifcbimtester/bimtester/locale/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-12-17 15:14+0100\n" +"POT-Creation-Date: 2020-12-23 11:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,121 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" +#: reports.py:153 reports.py:160 +msgid "OpenBIM auditing is a feature of" +msgstr "" + +#: reports.py:155 +msgid "en" +msgstr "" + +#: reports.py:156 +msgid "Success" +msgstr "" + +#: reports.py:157 +msgid "Failure" +msgstr "" + +#: reports.py:158 +msgid "Tests passed" +msgstr "" + +#: reports.py:159 +msgid "Duration" +msgstr "" + +#: reports.py:161 +msgid "and" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:24 +msgid "All {elemcount} elements in the file are {ifc_class}." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:32 +msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:41 +#: features/steps/utils.py:158 +msgid "Error in falsecount, something went wrong." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:80 +msgid "" +"For all {elemcount} {ifc_class} elements at least one of these class " +"attributes {parameter} has no value." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:81 +msgid "" +"For the following {falsecount} out of {elemcount} {ifc_class} elements at" +" least one of these class attributes {parameter} has no value: " +"{falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:82 +#: features/steps/attributes_eleclasses_methods.py:108 +#: features/steps/attributes_eleclasses_methods.py:133 +#: features/steps/attributes_psets_methods.py:31 +#: features/steps/geometric_detail_methods.py:52 +#: features/steps/geometric_detail_methods.py:127 +msgid "There are no {ifc_class} elements in the IFC file." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:106 +msgid "The name of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:107 +msgid "" +"The name of {falsecount} out of {elemcount} {ifc_class} elements is not " +"set: {falseelems}" +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:131 +msgid "The description of all {elemcount} {elemcount} elements is not set." +msgstr "" + +#: features/steps/attributes_eleclasses_methods.py:132 +msgid "" +"The description of {falsecount} out of {elemcount} {ifc_class} elements " +"is not set: {falseelems}" +msgstr "" + +#: features/steps/attributes_psets_methods.py:29 +msgid "" +"All {elemcount} {ifc_class} elements are missing the property {parameter}" +" in the pset." +msgstr "" + +#: features/steps/attributes_psets_methods.py:30 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are " +"missing the property {parameter} in the pset: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:50 +msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation." +msgstr "" + +#: features/steps/geometric_detail_methods.py:51 +msgid "" +"The following {falsecount} of {elemcount} {ifc_class} elements are not a " +"{parameter} representation: {falseelems}" +msgstr "" + +#: features/steps/geometric_detail_methods.py:125 +msgid "The geometry of all {elemcount} {ifc_class} elements have errors." +msgstr "" + +#: features/steps/geometric_detail_methods.py:126 +msgid "" +"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements " +"have errors: {falseelems}" +msgstr "" + #: features/steps/ifcdata_methods.py:7 msgid "We expected a schema of {} but instead got {}" msgstr "" diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index 201e9d12f9..d0672b8f9b 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -1,8 +1,11 @@ import datetime +import gettext # noqa import json import os import pystache +from .features.steps.utils import switch_locale + def generate_report( adir=".", @@ -13,6 +16,12 @@ def generate_report( # print("# Generating HTML reports now.") + # get locale path + localedir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "locale" + ) + # get html template path report_template_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), @@ -120,29 +129,15 @@ def generate_report( # workaround for retrieving the feature file language print(feature["keyword"]) if feature["keyword"] == "Feature": - strings_file_report = os.path.join( - report_template_path, - "strings_template_en.json" - ) + switch_locale(localedir, "en") elif feature["keyword"] == "Funktionalität": - strings_file_report = os.path.join( - report_template_path, - "strings_template_de.json" - ) + switch_locale(localedir, "de") elif feature["keyword"] == "Fonctionnalité": - strings_file_report = os.path.join( - report_template_path, - "strings_template_fr.json" - ) + switch_locale(localedir, "fr") else: # standard English - strings_file_report = os.path.join( - report_template_path, - "strings_template_en.json" - ) - strings_report = json.loads( - open(strings_file_report, encoding="utf8").read() - ) + switch_locale(localedir, "en") + strings_report = get_html_template_strings() # print(strings_report) data.update(strings_report) # print(data) @@ -152,3 +147,16 @@ def generate_report( with open(html_report, "w", encoding="utf8") as out: with open(html_tmpl, encoding="utf8") as template: out.write(pystache.render(template.read(), data)) + + +def get_html_template_strings(): + print(_("OpenBIM auditing is a feature of")) + return { + "tr_lang": _("en"), + "tr_success": _("Success"), + "tr_failure": _("Failure"), + "tr_tests_passed": _("Tests passed"), + "tr_duration": _("Duration"), + "tr_auditing": _("OpenBIM auditing is a feature of"), + "tr_and": _("and") + } diff --git a/src/ifcbimtester/bimtester/resources/reports/strings_template_de.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_de.json deleted file mode 100644 index cf6e29d294..0000000000 --- a/src/ifcbimtester/bimtester/resources/reports/strings_template_de.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tr_lang": "de", - "tr_success": "Bestanden", - "tr_failure": "Durchgefallen", - "tr_tests_passed": "Erfolgreiche Tests", - "tr_duration": "Dauer", - "tr_auditing": "OpenBIM auditing ist eine Funktionalität von", - "tr_and": "und" -} diff --git a/src/ifcbimtester/bimtester/resources/reports/strings_template_en.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_en.json deleted file mode 100644 index 745eb70a68..0000000000 --- a/src/ifcbimtester/bimtester/resources/reports/strings_template_en.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tr_lang": "en", - "tr_success": "Success", - "tr_failure": "Failure", - "tr_tests_passed": "Tests passed", - "tr_duration": "Duration", - "tr_auditing": "OpenBIM auditing is a feature of", - "tr_and": "and" -} diff --git a/src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json b/src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json deleted file mode 100644 index fa0a984bbb..0000000000 --- a/src/ifcbimtester/bimtester/resources/reports/strings_template_fr.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tr_lang": "fr", - "tr_success": "Succès", - "tr_failure": "Échec", - "tr_tests_passed": "Tests réussis", - "tr_duration": "Durée", - "tr_auditing": "L'audit OpenBIM auditing est une fonctionnalité de", - "tr_and": "et" -} From 1bea18d3cf886999abca92293dfd2da11f9053e1 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Thu, 24 Dec 2020 19:50:36 +0700 Subject: [PATCH 40/82] fixed #1206, #1207 decorating incomplete and zero segments --- src/ifcblenderexport/blenderbim/bim/decoration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index d8d478c26a..5038c9b61d 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -181,6 +181,8 @@ class BaseDecorator(): idx = 0 for spline in obj.data.splines: spline_points = spline.bezier_points if spline.bezier_points else spline.points + if len(spline_points) < 2: + continue points = [obj.matrix_world @ p.co for p in spline_points] cnt = len(points) vertices.extend(p[:3] for p in points) @@ -368,11 +370,13 @@ class DimensionDecorator(BaseDecorator): for i0, i1 in indices: v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) - length = (v1 - v0).length - text = self.format_value(length) p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 + if dir.length < 1: + continue + length = (v1 - v0).length + text = self.format_value(length) self.draw_label(text, p0 + (dir) * .5, dir) From 1b9b005820149e117c6598d71d07780889f4c005 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Thu, 24 Dec 2020 21:04:44 +0700 Subject: [PATCH 41/82] decorations colour. close #1197 --- .../blenderbim/bim/decoration.py | 59 ++++++++++--------- src/ifcblenderexport/blenderbim/bim/prop.py | 3 + src/ifcblenderexport/blenderbim/bim/ui.py | 4 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 5038c9b61d..41927d6a2f 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -19,6 +19,26 @@ class BaseDecorator(): # base name of objects to decorate basename = "IfcAnnotation/Something" + DEF_GLSL = """ + #define PI 3.141592653589793 + #define CIRCLE_SEGS 24 + // max points = SEGS (circle) + 2 (stem) + 3 (arrow) + #define MAX_POINTS 29 + #define ARROW1_ANGLE PI / 12.0 + #define ARROW2_ANGLE PI / 3.0 + #define ARROW1_SIZE 16.0 + #define ARROW2_SIZE 24.0 + #define CIRCLE1_SIZE 8.0 + #define CIRCLE2_SIZE 8.0 + #define DASH1_SIZE 16.0 + #define DASH2_SIZE 48.0 + #define DASH_GAP 4.0 + #define CROSS_SIZE 16.0 + #define CALLOUT_GAP 8.0 + #define CALLOUT_SIZE 64.0 + #define COLOR vec4({color[0]}, {color[1]}, {color[2]}, {color[3]}) + """ + VERT_GLSL = """ uniform mat4 viewMatrix; in vec3 pos; @@ -31,6 +51,7 @@ class BaseDecorator(): type = topo; } """ + GEOM_GLSL = """ layout(lines) in; layout(line_strip, max_vertices=2) out; @@ -49,7 +70,7 @@ class BaseDecorator(): out vec4 fragColor; void main() { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); + fragColor = COLOR; } """ @@ -91,30 +112,13 @@ class BaseDecorator(): } """ - DEF_GLSL = """ - #define PI 3.141592653589793 - #define CIRCLE_SEGS 24 - // max points = SEGS (circle) + 2 (stem) + 3 (arrow) - #define MAX_POINTS 29 - #define ARROW1_ANGLE PI / 12.0 - #define ARROW2_ANGLE PI / 3.0 - #define ARROW1_SIZE 16.0 - #define ARROW2_SIZE 24.0 - #define CIRCLE1_SIZE 8.0 - #define CIRCLE2_SIZE 8.0 - #define DASH1_SIZE 16.0 - #define DASH2_SIZE 48.0 - #define DASH_GAP 4.0 - #define CROSS_SIZE 16.0 - #define CALLOUT_GAP 8.0 - #define CALLOUT_SIZE 64.0 - """ - # class var for single handler 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') @@ -134,13 +138,13 @@ class BaseDecorator(): self.font_size = 16 self.dpi = context.preferences.system.dpi - @classmethod - def create_shader(cls): + def create_shader(self): + defines = self.DEF_GLSL.format(color=self.props.decorations_colour) # NB: libcode param doesn't work - return GPUShader(vertexcode=cls.VERT_GLSL, - fragcode=cls.FRAG_GLSL, - geocode=cls.LIB_GLSL + cls.GEOM_GLSL, - defines=cls.DEF_GLSL) + return GPUShader(vertexcode=self.VERT_GLSL, + fragcode=self.FRAG_GLSL, + geocode=self.LIB_GLSL + self.GEOM_GLSL, + defines=defines) def __call__(self): if self.props.active_drawing_index is None or len(self.props.drawings) == 0: @@ -280,6 +284,7 @@ class BaseDecorator(): blf.position(self.font_id, pos.x, pos.y, 0) blf.rotation(self.font_id, ang) + blf.color(self.font_id, *self.props.decorations_colour) blf.draw(self.font_id, text) blf.disable(self.font_id, blf.ROTATION) @@ -580,7 +585,7 @@ class HiddenDecorator(BaseDecorator): if (t > 0.5) { discard; } else { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); + fragColor = COLOR; } } """ diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index e0f8ecf986..017d494c19 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -723,6 +723,9 @@ class DocProperties(PropertyGroup): ifc_files: CollectionProperty(name="IFCs", type=StrProperty) drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations) + decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), + min=0.0, max=1.0, size=4, + update=toggleDecorations) class BIMCameraProperties(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index c8ea3dc1ea..79bd7b63d7 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2461,8 +2461,8 @@ class BIM_PT_annotation_utilities(Panel): row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index") - row = layout.row() - row.prop(props, "should_draw_decorations") + layout.prop(props, "should_draw_decorations") + layout.prop(props, "decorations_colour") class BIM_PT_qto_utilities(Panel): From 119a7293199141a0b19c3e0a5a337f8f6f38c04a Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Thu, 24 Dec 2020 21:55:27 +0700 Subject: [PATCH 42/82] fix #1165 imperial formatting --- src/ifcblenderexport/blenderbim/bim/helper.py | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/helper.py b/src/ifcblenderexport/blenderbim/bim/helper.py index 9b20112dc1..e920bdcf67 100644 --- a/src/ifcblenderexport/blenderbim/bim/helper.py +++ b/src/ifcblenderexport/blenderbim/bim/helper.py @@ -215,27 +215,20 @@ def format_distance(value, isArea=False, hide_units=True): feet += 1 inches = 0 - if inches != 0: - inchesString = str(inches) - if frac != 0: - inchesString += "-" - else: - inchesString += '"' - else: - inchesString = "" - - if feet != 0: - feetString = str(feet) + "' " - else: - feetString = "" - - if frac != 0: - fracString = str(frac) + "/" + str(base) + '"' - else: - fracString = "" - if not isArea: - tx_dist = feetString + inchesString + fracString + tx_dist = "" + if feet: + tx_dist += str(feet) + "'" + if feet and inches: + tx_dist += " - " + if inches: + tx_dist += str(inches) + if inches and frac: + tx_dist += " " + if frac: + tx_dist += str(frac) + "/" + str(base) + if inches or frac: + tx_dist += '"' else: tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft." From 877bf3207a120672fe1af89f068ab2e983f590ce Mon Sep 17 00:00:00 2001 From: aothms Date: Thu, 24 Dec 2020 20:46:42 +0000 Subject: [PATCH 43/82] Nix build: no longer build cmake, some fixes for 32bit crosscompiling --- nix/build-all.py | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/nix/build-all.py b/nix/build-all.py index b9a100f672..5033c2b2a4 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -23,7 +23,7 @@ # This script builds IfcOpenShell and its dependencies # # # # Prerequisites for this script to function correctly: # -# * git * bzip2 * tar * c(++) compilers * yacc * autoconf # +# * cmake * git * bzip2 * tar * c(++) compilers * yacc * autoconf # # # # if building with USE_OCCT additionally: # # * freetype * glx.h # @@ -38,15 +38,15 @@ # * libffi(-dev[el]) # # # # on debian 7.8 these can be obtained with: # -# $ apt-get install git gcc g++ autoconf bison bzip2 # +# $ apt-get install git gcc g++ autoconf bison bzip2 cmake # # libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev # # # # on ubuntu 14.04: # -# $ apt-get install git gcc g++ autoconf bison make # +# $ apt-get install git gcc g++ autoconf bison make cmake # # libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev # # # # on OS X El Capitan with homebrew: # -# $ brew install git bison autoconf automake freetype libffi # +# $ brew install git bison autoconf automake freetype libffi cmake # # # ############################################################################### @@ -92,8 +92,6 @@ BOOST_VERSION="1.59.0" PCRE_VERSION="8.41" #LIBXML2_VERSION="2.9.3" LIBXML2_VERSION="2.9.9" -CMAKE_VERSION="3.4.3" -#CMAKE_VERSION="3.14.5" SWIG_VERSION="3.0.12" #SWIG_VERSION="4.0.0" #OPENCOLLADA_VERSION="v1.6.63" @@ -303,7 +301,6 @@ def run(cmds, cwd=None): return stdout.strip() BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_") -CMAKE_VERSION_2=CMAKE_VERSION[:CMAKE_VERSION.rindex('.')] OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,) BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE) @@ -322,8 +319,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None): P=".." else: P=cmake_dir - cmake_path= os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake") - run([cmake_path, P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd) + run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd) def git_clone_or_pull_repository(clone_url, target_dir, revision=None): """Lazily clones the `git` repository denoted by `clone_url` into @@ -424,7 +420,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d else: raise ValueError() logger.info("\rBuilding %s... " % (name,)) - run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,)], cwd=extract_build_dir) + run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir) logger.info( "\rInstalling %s... " % (name,)) run([make, "install"], cwd=extract_build_dir) logger.info( "\rInstalled %s \n" % (name,)) @@ -445,7 +441,10 @@ cecho("Collecting dependencies:", GREEN) ADDITIONAL_ARGS=[] BOOST_ADDRESS_MODEL=[] if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64": - ADDITIONAL_ARGS=["-m32", "-arch i386"] + if get_os() == "Darwin": + ADDITIONAL_ARGS=["-m32", "-arch i386"] + else: + ADDITIONAL_ARGS=["-m32"] BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"] if get_os() == "Darwin": @@ -484,25 +483,8 @@ os.environ["CFLAGS"] = CFLAGS os.environ["LDFLAGS"] = LDFLAGS # Some dependencies need a more recent CMake version than most distros provide -build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,)) - -# Extract compiler flags from CMake to harmonize settings with other autoconf dependencies -CMAKE_FLAG_EXTRACT_DIR="ifcopenshell_cmake_test_%s" % (time.time(),) -# was sp.check_output([bash, "-c", "cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 32"]), in bash script, unclear what the exact required format is and whether it's needed -if os.path.exists(CMAKE_FLAG_EXTRACT_DIR): - shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR) -os.makedirs(CMAKE_FLAG_EXTRACT_DIR) -BUILD_CFG_UPPER=BUILD_CFG.upper() -for FL in ["C", "CXX"]: - run([bash, "-c", """echo " - message(\"\${CMAKE_%s_FLAGS_%s}\") - " > CMakeLists.txt""" % (FL, BUILD_CFG_UPPER)], cwd=CMAKE_FLAG_EXTRACT_DIR) - FL="%sFLAGS" % (FL,) - FLM="%sFLAGS_MINIMAL" % (FL,) -# @TODO: bash code unclear -# exec("%sFLAGS=%s" % (FL, sp.check_output([os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake"), "." -# declare ${FL}FLAGS_MINIMAL="`$DEPS_DIR/install/cmake-$CMAKE_VERSION/bin/cmake . 2>&1 >/dev/null` ${!FLM}" -shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR) +# @tfk: this is no longer needed +# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,)) if "json" in targets: json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals()) @@ -527,7 +509,7 @@ if "swig" in targets: build_dependency( name="swig", mode="autoconf", - build_tool_args=["--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())], + build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())], download_url="https://github.com/swig/swig.git", download_name="swig", download_tool=download_tool_git, From f235f5765b89976d32aaad006352f8ece20d3565 Mon Sep 17 00:00:00 2001 From: htlcnn Date: Sat, 26 Dec 2020 11:08:07 +0700 Subject: [PATCH 44/82] fix IfcGetAttribute malfunction --- src/ifcsverchok/nodes/ifc/get_attribute.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ifcsverchok/nodes/ifc/get_attribute.py b/src/ifcsverchok/nodes/ifc/get_attribute.py index 8c7280fc8f..deb98421d2 100644 --- a/src/ifcsverchok/nodes/ifc/get_attribute.py +++ b/src/ifcsverchok/nodes/ifc/get_attribute.py @@ -19,9 +19,15 @@ class SvIfcGetAttribute(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper self.outputs.new("SvStringsSocket", "value") def process(self): - self.sv_input_names = ["entity", "attribute_name"] self.value_out = [] - super().process() + entity_nested_inputs = self.inputs["entity"].sv_get() + attribute_name = self.inputs["attribute_name"].sv_get()[0][0] + for entities in entity_nested_inputs: + if hasattr(entities, "__iter__"): + for entity in entities: + self.process_ifc(entity, attribute_name) + else: + self.process_ifc(entities, attribute_name) self.outputs["value"].sv_set(self.value_out) def process_ifc(self, entity, attribute_name): From 315b32dd38e036e455b2aa74109c46464fbe2026 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sat, 26 Dec 2020 13:49:57 +0100 Subject: [PATCH 45/82] Nix build: assert cmake in path --- nix/build-all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/build-all.py b/nix/build-all.py index 5033c2b2a4..a943e25f94 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -257,7 +257,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t))))) # Check that required tools are in PATH -for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch"]: +for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]: if which(cmd) is None: raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,)) From 3c2c591d6dfd86d306a1e1c738c368c685e33f7a Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Sat, 26 Dec 2020 21:59:57 +0700 Subject: [PATCH 46/82] breakline annotation close #1208 --- .../blenderbim/bim/annotation.py | 59 ++++++++++++++++++- .../blenderbim/bim/operator.py | 5 +- src/ifcblenderexport/blenderbim/bim/ui.py | 5 ++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/annotation.py b/src/ifcblenderexport/blenderbim/bim/annotation.py index 5376f37a39..381628877a 100644 --- a/src/ifcblenderexport/blenderbim/bim/annotation.py +++ b/src/ifcblenderexport/blenderbim/bim/annotation.py @@ -22,7 +22,7 @@ class Annotator: obj = bpy.data.objects.new("IfcAnnotation/Text", curve) obj.matrix_world = bpy.context.scene.camera.matrix_world if related_element is None: - location, co2 = Annotator.get_placeholder_coords() + location, _, _, _ = Annotator.get_placeholder_coords() else: obj.data.BIMTextProperties.related_element = related_element location = related_element.location @@ -66,7 +66,7 @@ class Annotator: @staticmethod def add_line_to_annotation(obj, co1=None, co2=None): if co1 is None: - co1, co2 = Annotator.get_placeholder_coords() + co1, co2, _, _ = Annotator.get_placeholder_coords() co1 = obj.matrix_world.inverted() @ co1 co2 = obj.matrix_world.inverted() @ co2 if isinstance(obj.data, bpy.types.Mesh): @@ -82,6 +82,55 @@ class Annotator: polyline.points[-1].co = list(co2) + [1] return obj + @staticmethod + def add_plane_to_annotation(obj): + co1, co2, co3, co4 = Annotator.get_placeholder_coords() + co1 = obj.matrix_world.inverted() @ co1 # bot left + co2 = obj.matrix_world.inverted() @ co2 # top left + co3 = obj.matrix_world.inverted() @ co3 # bot right + co4 = obj.matrix_world.inverted() @ co4 # top right + + obj.data.vertices.add(4) + v1 = obj.data.vertices[-4] + v2 = obj.data.vertices[-3] + v3 = obj.data.vertices[-2] + v4 = obj.data.vertices[-1] + v1.co = co4 + v2.co = co2 + v3.co = co1 + v4.co = co3 + + obj.data.edges.add(4) + e1 = obj.data.edges[-4] + e2 = obj.data.edges[-3] + e3 = obj.data.edges[-2] + e4 = obj.data.edges[-1] + e1.vertices = (v1.index, v2.index) + e2.vertices = (v2.index, v3.index) + e3.vertices = (v3.index, v4.index) + e4.vertices = (v4.index, v1.index) + + obj.data.loops.add(4) + l1 = obj.data.loops[-4] + l2 = obj.data.loops[-3] + l3 = obj.data.loops[-2] + l4 = obj.data.loops[-1] + l1.vertex_index = v1.index + l1.edge_index = e1.index + l2.vertex_index = v2.index + l2.edge_index = e2.index + l3.vertex_index = v3.index + l3.edge_index = e3.index + l4.vertex_index = v4.index + l4.edge_index = e4.index + + obj.data.polygons.add(1) + p1 = obj.data.polygons[-1] + p1.vertices = (v1.index, v2.index, v3.index, v4.index) + p1.loop_start = l1.index + p1.loop_total = 4 + return obj + @staticmethod def get_annotation_obj(name, data_type): collection = bpy.context.scene.camera.users_collection[0] @@ -111,4 +160,8 @@ class Annotator: else: y = camera.data.ortho_scale / 4 y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0)) - return (camera.location + z_offset, camera.location + z_offset + y_offset) + x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0)) + return (camera.location + z_offset, + camera.location + z_offset + y_offset, + camera.location + z_offset + x_offset, + camera.location + z_offset + x_offset + y_offset) \ No newline at end of file diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index ad1ed48e43..3f963ec85e 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3756,7 +3756,10 @@ class AddAnnotation(bpy.types.Operator): obj = annotation.Annotator.add_text() else: obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type) - obj = annotation.Annotator.add_line_to_annotation(obj) + if self.obj_name == 'Break': + obj = annotation.Annotator.add_plane_to_annotation(obj) + else: + obj = annotation.Annotator.add_line_to_annotation(obj) bpy.ops.object.select_all(action="DESELECT") bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode="EDIT") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 79bd7b63d7..13b71347ab 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2448,6 +2448,11 @@ class BIM_PT_annotation_utilities(Panel): op.obj_name = "Section Level" op.data_type = "curve" + row = layout.row(align=True) + op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE") + op.obj_name = "Break" + op.data_type = "mesh" + props = bpy.context.scene.DocProperties row = layout.row(align=True) From abaae7f06df3cd012d0aa51a8a82e17bff2dd06b Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Sat, 26 Dec 2020 23:25:39 +0700 Subject: [PATCH 47/82] copying grid. close #1210 --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 20 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 2 ++ 3 files changed, 23 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 62eb4030aa..7ea01eae49 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -245,6 +245,7 @@ if bpy is not None: operator.CleanWireframes, operator.LinkIfc, operator.SnapSpacesTogether, + operator.CopyGrid, prop.StrProperty, prop.Attribute, prop.MaterialLayer, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 3f963ec85e..5661c03e48 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1,3 +1,4 @@ +from itertools import chain import os import bpy import bcf @@ -5198,3 +5199,22 @@ class SnapSpacesTogether(bpy.types.Operator): for v in polygon.vertices: obj.data.vertices[v].co += offset return {"FINISHED"} + + +class CopyGrid(bpy.types.Operator): + bl_idname = "bim.add_grid" + bl_label = "Add Grid" + + def execute(self, context): + props = context.scene.DocProperties + if props.active_drawing_index is None or len(props.drawings) == 0: + return {"CANCELED"} + drawing = props.drawings[props.active_drawing_index] + collection = bpy.data.collections.get("IfcGroup/" + drawing.name) + + for coll in bpy.data.collections: + if coll.name.startswith('IfcGrid'): + for obj in coll.all_objects: + if obj.type == 'MESH': + collection.objects.link(obj.copy()) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 13b71347ab..d80e32f013 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2416,6 +2416,8 @@ class BIM_PT_annotation_utilities(Panel): row.operator("bim.clean_wireframes") row = layout.row(align=True) row.operator("bim.link_ifc") + row = layout.row(align=True) + row.operator("bim.add_grid") row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT") From 7e306251c1f3f26581b1cbed70c75922f3f9ce5e Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Sat, 26 Dec 2020 23:41:10 +0700 Subject: [PATCH 48/82] Misc annotation and decoration. close #1209 --- src/ifcblenderexport/blenderbim/bim/decoration.py | 7 +++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 2 ++ src/ifcblenderexport/blenderbim/bim/ui.py | 3 +++ 3 files changed, 12 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 41927d6a2f..68d1afa855 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -598,6 +598,13 @@ class HiddenDecorator(BaseDecorator): self.draw_lines(obj, verts, idxs) +class MiscDecorator(HiddenDecorator): + basename = "IfcAnnotation/Misc" + installed = None + + FRAG_GLSL = BaseDecorator.FRAG_GLSL + + class LevelDecorator(BaseDecorator): def get_splines(self, obj): diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 017d494c19..1ebfc3ff5c 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -368,6 +368,7 @@ def toggleDecorations(self, context): decoration.HiddenDecorator.install(self, context) decoration.PlanDecorator.install(self, context) decoration.SectionDecorator.install(self, context) + decoration.MiscDecorator.install(self, context) else: decoration.DimensionDecorator.uninstall() decoration.EqualityDecorator.uninstall() @@ -376,6 +377,7 @@ def toggleDecorations(self, context): decoration.HiddenDecorator.uninstall() decoration.PlanDecorator.uninstall() decoration.SectionDecorator.uninstall() + decoration.MiscDecorator.uninstall() def getScenarios(self, context): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index d80e32f013..702d2dd086 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2454,6 +2454,9 @@ class BIM_PT_annotation_utilities(Panel): op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE") op.obj_name = "Break" op.data_type = "mesh" + op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY") + op.obj_name = "Misc" + op.data_type = "mesh" props = bpy.context.scene.DocProperties From 5234c8086f227a6753552a8e76e84ead6f5612ad Mon Sep 17 00:00:00 2001 From: Maxim Vasilyev Date: Sun, 27 Dec 2020 00:21:59 +0700 Subject: [PATCH 49/82] fixed typo --- src/ifcblenderexport/blenderbim/bim/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 5661c03e48..2425e6d9a1 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -5208,7 +5208,7 @@ class CopyGrid(bpy.types.Operator): def execute(self, context): props = context.scene.DocProperties if props.active_drawing_index is None or len(props.drawings) == 0: - return {"CANCELED"} + return {"CANCELLED"} drawing = props.drawings[props.active_drawing_index] collection = bpy.data.collections.get("IfcGroup/" + drawing.name) From e51b7d3c85569f33e6cbe5e3e03a970d6dcb9931 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 27 Dec 2020 16:20:35 +1100 Subject: [PATCH 50/82] Implement ability to add BCF header file --- src/bcf/bcf/bcfxml.py | 13 +++++++ .../blenderbim/bim/__init__.py | 2 + .../blenderbim/bim/operator.py | 38 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 3 ++ src/ifcblenderexport/blenderbim/bim/ui.py | 28 ++++++++++++-- 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 6d57f8975b..92a8cc7771 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -513,6 +513,19 @@ class BcfXml: os.remove(filepath) self.edit_topic(topic) + def add_file(self, topic, header_file): + if os.path.exists(header_file.reference): + topic_filepath = os.path.join(self.filepath, topic.guid) + header_file.filename = os.path.basename(header_file.reference) + copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename)) + header_file.reference = header_file.filename + header_file.is_external = False + header_file.date = datetime.utcnow().isoformat() + if not topic.header: + topic.header = bcf.data.Header() + topic.header.files.append(header_file) + self.edit_topic(topic) + def get_comments(self, guid): comments = {} data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd") diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 7ea01eae49..e388735cf7 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -35,6 +35,7 @@ if bpy is not None: operator.EditBcfTopic, operator.SaveBcfProject, operator.AddBcfTopic, + operator.AddBcfHeaderFile, operator.ViewBcfTopic, operator.RemoveBcfComment, operator.EditBcfComment, @@ -45,6 +46,7 @@ if bpy is not None: operator.RemoveBcfFile, operator.OpenUri, operator.OpenBcfReferenceLink, + operator.SelectBcfHeaderFile, operator.SelectFeaturesDir, operator.SelectDiffJsonFile, operator.SelectDiffNewFile, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 2425e6d9a1..d2af9239aa 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -730,6 +730,26 @@ class AddBcfTopic(bpy.types.Operator): return {"FINISHED"} +class AddBcfHeaderFile(bpy.types.Operator): + bl_idname = "bim.add_bcf_header_file" + bl_label = "Add BCF Header File" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + header_file = bcf.data.HeaderFile() + header_file.reference = blender_topic.file_reference + if not os.path.exists(header_file.reference): + header_file.filename = header_file.reference + header_file.ifc_project = blender_topic.file_ifc_project + header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element + bcfxml.add_file(topic, header_file) + props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + return {"FINISHED"} + + class ViewBcfTopic(bpy.types.Operator): bl_idname = "bim.view_bcf_topic" bl_label = "Get BCF Topic" @@ -2308,6 +2328,24 @@ class SelectSmartGroup(bpy.types.Operator): return {"FINISHED"} +class SelectBcfHeaderFile(bpy.types.Operator): + bl_idname = "bim.select_bcf_header_file" + bl_label = "Select BCF Header File" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + target: bpy.props.StringProperty() + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.file_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + class SelectFeaturesDir(bpy.types.Operator): bl_idname = "bim.select_features_dir" bl_label = "Select Features Directory" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 1ebfc3ff5c..2bd3ce8f7e 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1013,6 +1013,9 @@ class BcfTopic(PropertyGroup): description: StringProperty(default="", name="Description") viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") files: CollectionProperty(name="Files", type=StrProperty) + file_reference: StringProperty(default="", name="Reference") + file_ifc_project: StringProperty(default="", name="IFC Project") + file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") reference_links: CollectionProperty(name="Reference Links", type=StrProperty) labels: CollectionProperty(name="Labels", type=StrProperty) bim_snippet: PointerProperty(type=BcfBimSnippet) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 702d2dd086..64a1b7fe26 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1759,9 +1759,31 @@ class BIM_PT_bcf_metadata(Panel): op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) row.operator("bim.remove_bcf_file", icon="X", text="").index = index - box.label(text=f.date) - # box.label(text=f.ifc_project) - # box.label(text=f.ifc_spatial_structure_element) + + row = box.row() + row.label(text="Date") + row.label(text=f.date) + + if f.ifc_project: + row = box.row() + row.label(text="IFC Project") + row.label(text=f.ifc_project) + + if f.ifc_spatial_structure_element: + row = box.row() + row.label(text="IFC Spatial Structure Element") + row.label(text=f.ifc_spatial_structure_element) + + row = layout.row(align=True) + row.prop(topic, "file_reference") + row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "file_ifc_project") + row = layout.row() + row.prop(topic, "file_ifc_spatial_structure_element") + + row = layout.row() + row.operator("bim.add_bcf_header_file") if topic.reference_links: layout.label(text="Reference Links:") From b9583c562d4067c0d30206f72f31795552b25c21 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 27 Dec 2020 16:53:40 +1100 Subject: [PATCH 51/82] Implement support for deleting reference links --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 23 +++++++++++++++++-- src/ifcblenderexport/blenderbim/bim/ui.py | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index e388735cf7..16b5b0dba3 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -38,6 +38,7 @@ if bpy is not None: operator.AddBcfHeaderFile, operator.ViewBcfTopic, operator.RemoveBcfComment, + operator.RemoveBcfReferenceLink, operator.EditBcfComment, operator.AddBcfComment, operator.ActivateBcfViewpoint, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index d2af9239aa..7fa8272628 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -743,8 +743,10 @@ class AddBcfHeaderFile(bpy.types.Operator): header_file.reference = blender_topic.file_reference if not os.path.exists(header_file.reference): header_file.filename = header_file.reference - header_file.ifc_project = blender_topic.file_ifc_project - header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element + if len(blender_topic.file_ifc_project) == 22: + header_file.ifc_project = blender_topic.file_ifc_project + if len(blender_topic.file_ifc_spatial_structure_element) == 22: + header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element bcfxml.add_file(topic, header_file) props.active_topic_index = props.active_topic_index # refreshes the BCF Topic return {"FINISHED"} @@ -839,6 +841,23 @@ class RemoveBcfFile(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfReferenceLink(bpy.types.Operator): + bl_idname = "bim.remove_bcf_reference_link" + bl_label = "Remove BCF Reference Link" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.reference_links[self.index] + bcfxml.edit_topic(topic) + blender_topic.reference_links.remove(self.index) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 64a1b7fe26..1beaf049ac 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1791,6 +1791,7 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row(align=True) row.prop(link, "name") row.operator("bim.open_uri", icon="URL", text="").uri = link.name + row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index if topic.labels: layout.label(text="Labels:") From 6a00f2f0d2c7d210a6f27c23bc81052f137a8157 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 27 Dec 2020 17:07:02 +1100 Subject: [PATCH 52/82] Implement adding BCF reference links --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 20 +++++- src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 62 ++++++++++--------- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 16b5b0dba3..13abc613bd 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -36,6 +36,7 @@ if bpy is not None: operator.SaveBcfProject, operator.AddBcfTopic, operator.AddBcfHeaderFile, + operator.AddBcfReferenceLink, operator.ViewBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfReferenceLink, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 7fa8272628..4cfd6be8ff 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -841,6 +841,25 @@ class RemoveBcfFile(bpy.types.Operator): return {"FINISHED"} +class AddBcfReferenceLink(bpy.types.Operator): + bl_idname = "bim.add_bcf_reference_link" + bl_label = "Add BCF Reference Link" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.reference_link: + return {"FINISHED"} + new = blender_topic.reference_links.add() + new.name = blender_topic.reference_link + topic.reference_links.append(blender_topic.reference_link) + bcfxml.edit_topic(topic) + blender_topic.reference_link = "" + return {"FINISHED"} + + class RemoveBcfReferenceLink(bpy.types.Operator): bl_idname = "bim.remove_bcf_reference_link" bl_label = "Remove BCF Reference Link" @@ -854,7 +873,6 @@ class RemoveBcfReferenceLink(bpy.types.Operator): del topic.reference_links[self.index] bcfxml.edit_topic(topic) blender_topic.reference_links.remove(self.index) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 2bd3ce8f7e..591adf4183 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1017,6 +1017,7 @@ class BcfTopic(PropertyGroup): file_ifc_project: StringProperty(default="", name="IFC Project") file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") reference_links: CollectionProperty(name="Reference Links", type=StrProperty) + reference_link: StringProperty(default="", name="Reference Link") labels: CollectionProperty(name="Labels", type=StrProperty) bim_snippet: PointerProperty(type=BcfBimSnippet) document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 1beaf049ac..a411f6b90a 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1747,32 +1747,31 @@ class BIM_PT_bcf_metadata(Panel): bcfxml = bcfstore.BcfStore.get_bcfxml() bcf_topic = bcfxml.topics[topic.name] - if bcf_topic.header and bcf_topic.header.files: - layout.label(text="Header Files:") - for index, f in enumerate(bcf_topic.header.files): - box = self.layout.box() - row = box.row(align=True) - row.label(text=f.filename, icon="FILE_BLANK") - if f.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = f.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) - row.operator("bim.remove_bcf_file", icon="X", text="").index = index + layout.label(text="Header Files:") + for index, f in enumerate(bcf_topic.header.files): + box = self.layout.box() + row = box.row(align=True) + row.label(text=f.filename, icon="FILE_BLANK") + if f.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = f.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) + row.operator("bim.remove_bcf_file", icon="X", text="").index = index + row = box.row() + row.label(text="Date") + row.label(text=f.date) + + if f.ifc_project: row = box.row() - row.label(text="Date") - row.label(text=f.date) + row.label(text="IFC Project") + row.label(text=f.ifc_project) - if f.ifc_project: - row = box.row() - row.label(text="IFC Project") - row.label(text=f.ifc_project) - - if f.ifc_spatial_structure_element: - row = box.row() - row.label(text="IFC Spatial Structure Element") - row.label(text=f.ifc_spatial_structure_element) + if f.ifc_spatial_structure_element: + row = box.row() + row.label(text="IFC Spatial Structure Element") + row.label(text=f.ifc_spatial_structure_element) row = layout.row(align=True) row.prop(topic, "file_reference") @@ -1785,13 +1784,16 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row() row.operator("bim.add_bcf_header_file") - if topic.reference_links: - layout.label(text="Reference Links:") - for index, link in enumerate(topic.reference_links): - row = layout.row(align=True) - row.prop(link, "name") - row.operator("bim.open_uri", icon="URL", text="").uri = link.name - row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index + layout.label(text="Reference Links:") + for index, link in enumerate(topic.reference_links): + row = layout.row(align=True) + row.prop(link, "name") + row.operator("bim.open_uri", icon="URL", text="").uri = link.name + row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "reference_link") + row = layout.row() + row.operator("bim.add_bcf_reference_link") if topic.labels: layout.label(text="Labels:") From 6cfa9f6e9be67c328a642e5c7150931ab7825cc5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 11:20:44 +1100 Subject: [PATCH 53/82] Implement ability to edit individual BCF reference links --- src/ifcblenderexport/blenderbim/bim/__init__.py | 2 ++ src/ifcblenderexport/blenderbim/bim/operator.py | 17 +++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 10 +++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 13abc613bd..2912567d05 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -40,6 +40,7 @@ if bpy is not None: operator.ViewBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfReferenceLink, + operator.EditBcfReferenceLinks, operator.EditBcfComment, operator.AddBcfComment, operator.ActivateBcfViewpoint, @@ -251,6 +252,7 @@ if bpy is not None: operator.SnapSpacesTogether, operator.CopyGrid, prop.StrProperty, + prop.BcfReferenceLink, prop.Attribute, prop.MaterialLayer, prop.MaterialConstituent, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 4cfd6be8ff..ae66656b95 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -860,6 +860,23 @@ class AddBcfReferenceLink(bpy.types.Operator): return {"FINISHED"} +class EditBcfReferenceLinks(bpy.types.Operator): + bl_idname = "bim.edit_bcf_reference_links" + bl_label = "Edit BCF Reference Link" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + for index, reference_link in enumerate(topic.reference_links): + if reference_link == blender_topic.reference_links[index].name: + continue + topic.reference_links[index] = blender_topic.reference_links[index].name + bcfxml.edit_topic(topic) + return {"FINISHED"} + + class RemoveBcfReferenceLink(bpy.types.Operator): bl_idname = "bim.remove_bcf_reference_link" bl_label = "Remove BCF Reference Link" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 591adf4183..eaecd710d9 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -167,6 +167,10 @@ def refreshPredefinedTypes(self, context): context.scene.BIMProperties.ifc_predefined_type = enum[0][0] +def updateBcfReferenceLink(self, context): + bpy.ops.bim.edit_bcf_reference_links() + + def getDiagramScales(self, context): global diagram_scales_enum if ( @@ -587,6 +591,10 @@ def refreshBcfTopic(self, context): getBcfViewpoints(self, context) +class BcfReferenceLink(PropertyGroup): + name: StringProperty(name="Name", update=updateBcfReferenceLink) + + class StrProperty(PropertyGroup): pass @@ -1016,7 +1024,7 @@ class BcfTopic(PropertyGroup): file_reference: StringProperty(default="", name="Reference") file_ifc_project: StringProperty(default="", name="IFC Project") file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") - reference_links: CollectionProperty(name="Reference Links", type=StrProperty) + reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) reference_link: StringProperty(default="", name="Reference Link") labels: CollectionProperty(name="Labels", type=StrProperty) bim_snippet: PointerProperty(type=BcfBimSnippet) From d5566785beebe338a4240c3eec3e24800976f5b6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 11:40:40 +1100 Subject: [PATCH 54/82] Implement BCF delete label --- src/ifcblenderexport/blenderbim/bim/__init__.py | 1 + src/ifcblenderexport/blenderbim/bim/operator.py | 16 ++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 10 +++++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 2912567d05..e778acf274 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -41,6 +41,7 @@ if bpy is not None: operator.RemoveBcfComment, operator.RemoveBcfReferenceLink, operator.EditBcfReferenceLinks, + operator.RemoveBcfLabel, operator.EditBcfComment, operator.AddBcfComment, operator.ActivateBcfViewpoint, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index ae66656b95..7cb91ca828 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -893,6 +893,22 @@ class RemoveBcfReferenceLink(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfLabel(bpy.types.Operator): + bl_idname = "bim.remove_bcf_label" + bl_label = "Remove BCF Label" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.labels[self.index] + bcfxml.edit_topic(topic) + blender_topic.labels.remove(self.index) + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index a411f6b90a..e08745fe6b 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1795,11 +1795,11 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row() row.operator("bim.add_bcf_reference_link") - if topic.labels: - layout.label(text="Labels:") - for index, label in enumerate(topic.labels): - row = layout.row(align=True) - row.prop(label, "name", text="") + layout.label(text="Labels:") + for index, label in enumerate(topic.labels): + row = layout.row(align=True) + row.prop(label, "name", text="") + row.operator("bim.remove_bcf_label", icon="X", text="").index = index if topic.bim_snippet.schema: layout.label(text="BIM Snippet:") From 3deba2e71e80e954c2ad8e96f724df450b0c9650 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 12:46:52 +1100 Subject: [PATCH 55/82] Implement support for adding BCF labels to topics --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 19 ++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 46 +++++++++++-------- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index e778acf274..edf31feb74 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -37,6 +37,7 @@ if bpy is not None: operator.AddBcfTopic, operator.AddBcfHeaderFile, operator.AddBcfReferenceLink, + operator.AddBcfLabel, operator.ViewBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfReferenceLink, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 7cb91ca828..2f26b8df11 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -860,6 +860,25 @@ class AddBcfReferenceLink(bpy.types.Operator): return {"FINISHED"} +class AddBcfLabel(bpy.types.Operator): + bl_idname = "bim.add_bcf_label" + bl_label = "Add BCF Label" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.label: + return {"FINISHED"} + new = blender_topic.labels.add() + new.name = blender_topic.label + topic.labels.append(blender_topic.label) + bcfxml.edit_topic(topic) + blender_topic.label = "" + return {"FINISHED"} + + class EditBcfReferenceLinks(bpy.types.Operator): bl_idname = "bim.edit_bcf_reference_links" bl_label = "Edit BCF Reference Link" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index eaecd710d9..21bc37f48b 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1027,6 +1027,7 @@ class BcfTopic(PropertyGroup): reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) reference_link: StringProperty(default="", name="Reference Link") labels: CollectionProperty(name="Labels", type=StrProperty) + label: StringProperty(default="", name="Label") bim_snippet: PointerProperty(type=BcfBimSnippet) document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) related_topics: CollectionProperty(name="Related Topics", type=StrProperty) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index e08745fe6b..f3d9e77932 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1748,30 +1748,32 @@ class BIM_PT_bcf_metadata(Panel): bcf_topic = bcfxml.topics[topic.name] layout.label(text="Header Files:") - for index, f in enumerate(bcf_topic.header.files): - box = self.layout.box() - row = box.row(align=True) - row.label(text=f.filename, icon="FILE_BLANK") - if f.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = f.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) - row.operator("bim.remove_bcf_file", icon="X", text="").index = index - row = box.row() - row.label(text="Date") - row.label(text=f.date) + if bcf_topic.header: + for index, f in enumerate(bcf_topic.header.files): + box = self.layout.box() + row = box.row(align=True) + row.label(text=f.filename, icon="FILE_BLANK") + if f.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = f.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) + row.operator("bim.remove_bcf_file", icon="X", text="").index = index - if f.ifc_project: row = box.row() - row.label(text="IFC Project") - row.label(text=f.ifc_project) + row.label(text="Date") + row.label(text=f.date) - if f.ifc_spatial_structure_element: - row = box.row() - row.label(text="IFC Spatial Structure Element") - row.label(text=f.ifc_spatial_structure_element) + if f.ifc_project: + row = box.row() + row.label(text="IFC Project") + row.label(text=f.ifc_project) + + if f.ifc_spatial_structure_element: + row = box.row() + row.label(text="IFC Spatial Structure Element") + row.label(text=f.ifc_spatial_structure_element) row = layout.row(align=True) row.prop(topic, "file_reference") @@ -1800,6 +1802,10 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row(align=True) row.prop(label, "name", text="") row.operator("bim.remove_bcf_label", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "label") + row = layout.row() + row.operator("bim.add_bcf_label") if topic.bim_snippet.schema: layout.label(text="BIM Snippet:") From bca9c82c3d7e384749aa6fcde138c0d991b8edc8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 12:50:22 +1100 Subject: [PATCH 56/82] Implement ability to edit labels --- src/ifcblenderexport/blenderbim/bim/__init__.py | 2 ++ src/ifcblenderexport/blenderbim/bim/operator.py | 17 +++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 10 +++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index edf31feb74..cd0e27846f 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -42,6 +42,7 @@ if bpy is not None: operator.RemoveBcfComment, operator.RemoveBcfReferenceLink, operator.EditBcfReferenceLinks, + operator.EditBcfLabels, operator.RemoveBcfLabel, operator.EditBcfComment, operator.AddBcfComment, @@ -255,6 +256,7 @@ if bpy is not None: operator.CopyGrid, prop.StrProperty, prop.BcfReferenceLink, + prop.BcfLabel, prop.Attribute, prop.MaterialLayer, prop.MaterialConstituent, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 2f26b8df11..d0de452d8c 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -896,6 +896,23 @@ class EditBcfReferenceLinks(bpy.types.Operator): return {"FINISHED"} +class EditBcfLabels(bpy.types.Operator): + bl_idname = "bim.edit_bcf_labels" + bl_label = "Edit BCF Labels" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + for index, label in enumerate(topic.labels): + if label == blender_topic.labels[index].name: + continue + topic.labels[index] = blender_topic.labels[index].name + bcfxml.edit_topic(topic) + return {"FINISHED"} + + class RemoveBcfReferenceLink(bpy.types.Operator): bl_idname = "bim.remove_bcf_reference_link" bl_label = "Remove BCF Reference Link" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 21bc37f48b..fc45de7de2 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -171,6 +171,10 @@ def updateBcfReferenceLink(self, context): bpy.ops.bim.edit_bcf_reference_links() +def updateBcfLabel(self, context): + bpy.ops.bim.edit_bcf_labels() + + def getDiagramScales(self, context): global diagram_scales_enum if ( @@ -595,6 +599,10 @@ class BcfReferenceLink(PropertyGroup): name: StringProperty(name="Name", update=updateBcfReferenceLink) +class BcfLabel(PropertyGroup): + name: StringProperty(name="Name", update=updateBcfLabel) + + class StrProperty(PropertyGroup): pass @@ -1026,7 +1034,7 @@ class BcfTopic(PropertyGroup): file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) reference_link: StringProperty(default="", name="Reference Link") - labels: CollectionProperty(name="Labels", type=StrProperty) + labels: CollectionProperty(name="Labels", type=BcfLabel) label: StringProperty(default="", name="Label") bim_snippet: PointerProperty(type=BcfBimSnippet) document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) From 510d756cc48798f0a5ffb1a16c248c64954ff1fb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 13:14:33 +1100 Subject: [PATCH 57/82] Implement delete BCF topic BIM snippet --- src/bcf/bcf/bcfxml.py | 10 ++++++++++ src/ifcblenderexport/blenderbim/bim/__init__.py | 1 + src/ifcblenderexport/blenderbim/bim/operator.py | 16 ++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 7 ++++--- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 92a8cc7771..31aca111ef 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -513,6 +513,16 @@ class BcfXml: os.remove(filepath) self.edit_topic(topic) + def delete_bim_snippet(self, topic): + if not topic.bim_snippet: + return + if topic.bim_snippet.reference and not topic.bim_snippet.is_external: + filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference) + if os.path.exists(filepath): + os.remove(filepath) + topic.bim_snippet = None + self.edit_topic(topic) + def add_file(self, topic, header_file): if os.path.exists(header_file.reference): topic_filepath = os.path.join(self.filepath, topic.guid) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index cd0e27846f..2bd55bc823 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -40,6 +40,7 @@ if bpy is not None: operator.AddBcfLabel, operator.ViewBcfTopic, operator.RemoveBcfComment, + operator.RemoveBcfBimSnippet, operator.RemoveBcfReferenceLink, operator.EditBcfReferenceLinks, operator.EditBcfLabels, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index d0de452d8c..d8099288e3 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -945,6 +945,22 @@ class RemoveBcfLabel(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfBimSnippet(bpy.types.Operator): + bl_idname = "bim.remove_bcf_bim_snippet" + bl_label = "Remove BCF BIM Snippet" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_bim_snippet(topic) + blender_topic.bim_snippet.schema = "" + blender_topic.bim_snippet.reference = "" + blender_topic.bim_snippet.type = "" + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index f3d9e77932..76b81ba4dd 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1807,20 +1807,21 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row() row.operator("bim.add_bcf_label") + layout.label(text="BIM Snippet:") if topic.bim_snippet.schema: - layout.label(text="BIM Snippet:") row = layout.row(align=True) - row.prop(topic.bim_snippet, "type") + row.prop(topic.bim_snippet, "type", emboss=False) if topic.bim_snippet.schema: row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema row = layout.row(align=True) - row.prop(topic.bim_snippet, "reference") + row.prop(topic.bim_snippet, "reference", emboss=False) if topic.bim_snippet.is_external: row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference else: op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference) + row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") if topic.document_references: layout.label(text="Document References:") From f277733ff546f897cd0d2b10532d83c9db87b99c Mon Sep 17 00:00:00 2001 From: htlcnn Date: Mon, 28 Dec 2020 10:18:55 +0700 Subject: [PATCH 58/82] ifccsv delimiter --- src/ifcblenderexport/blenderbim/bim/operator.py | 4 ++++ src/ifcblenderexport/blenderbim/bim/prop.py | 6 ++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 5 +++++ src/ifccsv/ifccsv.py | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index d8099288e3..6c94cf4a9a 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3778,6 +3778,10 @@ class ExportIfcCsv(bpy.types.Operator): ifc_csv.output = self.filepath ifc_csv.attributes = [a.name for a in bpy.context.scene.BIMProperties.csv_attributes] ifc_csv.selector = selector + if bpy.context.scene.BIMProperties.csv_delimiter == "CUSTOM": + ifc_csv.delimiter = bpy.context.scene.BIMProperties.csv_custom_delimiter + else: + ifc_csv.delimiter = bpy.context.scene.BIMProperties.csv_delimiter ifc_csv.export(ifc_file, results) return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index fc45de7de2..ab6520bdd1 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1477,6 +1477,12 @@ class BIMProperties(PropertyGroup): ) ifc_selector: StringProperty(default="", name="IFC Selector") csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty) + csv_delimiter: EnumProperty( + items=[(";", ";", ""), (",", ",", ""), (".", ".", ""), ("CUSTOM", "Custom", ""),], + name="IFC CSV Delimiter", + default=";", + ) + csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter") document_information: CollectionProperty(name="Document Information", type=DocumentInformation) active_document_information_index: IntProperty(name="Active Document Information Index") document_references: CollectionProperty(name="Document References", type=DocumentReference) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 76b81ba4dd..7eeef9fbfd 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1642,6 +1642,11 @@ class BIM_PT_ifccsv(Panel): row.prop(attribute, "name", text="") row.operator("bim.remove_csv_attribute", icon="X", text="").index = index + row = layout.row(align=True) + row.prop(props, "csv_delimiter") + row = layout.row(align=True) + row.prop(props, "csv_custom_delimiter") + row = layout.row(align=True) row.operator("bim.export_ifccsv", icon="EXPORT") row.operator("bim.import_ifccsv", icon="IMPORT") diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 19aa94195a..26d66092a0 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -109,6 +109,7 @@ class IfcCsv: self.attributes = [] self.output = "" self.selector = None + self.delimiter = ";" def export(self, ifc_file, elements): self.ifc_file = ifc_file @@ -151,7 +152,7 @@ class IfcCsv: def Import(self, ifc): ifc_file = ifcopenshell.open(ifc) with open(self.output, newline="", encoding="utf-8") as f: - reader = csv.reader(f) + reader = csv.reader(f, delimiter=self.delimiter) headers = [] for row in reader: if not headers: From 36a0119f40ace06c46fb815f8b47b208f5793cac Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 16:03:34 +1100 Subject: [PATCH 59/82] Implement add BIM Snippet --- src/bcf/bcf/bcfxml.py | 14 ++ .../blenderbim/bim/__init__.py | 3 + .../blenderbim/bim/operator.py | 155 ++++++++++++------ src/ifcblenderexport/blenderbim/bim/prop.py | 3 + src/ifcblenderexport/blenderbim/bim/ui.py | 11 ++ 5 files changed, 137 insertions(+), 49 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 31aca111ef..c059917e2e 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -523,6 +523,20 @@ class BcfXml: topic.bim_snippet = None self.edit_topic(topic) + def add_bim_snippet(self, topic, bim_snippet): + if topic.bim_snippet: + self.delete_bim_snippet(topic) + if os.path.exists(bim_snippet.reference): + topic_filepath = os.path.join(self.filepath, topic.guid) + filename = os.path.basename(bim_snippet.reference) + copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename)) + bim_snippet.reference = filename + bim_snippet.is_external = False + else: + bim_snippet.is_external = True + topic.bim_snippet = bim_snippet + self.edit_topic(topic) + def add_file(self, topic, header_file): if os.path.exists(header_file.reference): topic_filepath = os.path.join(self.filepath, topic.guid) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 2bd55bc823..c8cfc85b86 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -28,6 +28,7 @@ if bpy is not None: operator.NewBcfProject, operator.LoadBcfProject, operator.LoadBcfTopics, + operator.LoadBcfTopic, operator.LoadBcfComments, operator.EditBcfProjectName, operator.EditBcfAuthor, @@ -36,6 +37,7 @@ if bpy is not None: operator.SaveBcfProject, operator.AddBcfTopic, operator.AddBcfHeaderFile, + operator.AddBcfBimSnippet, operator.AddBcfReferenceLink, operator.AddBcfLabel, operator.ViewBcfTopic, @@ -54,6 +56,7 @@ if bpy is not None: operator.OpenUri, operator.OpenBcfReferenceLink, operator.SelectBcfHeaderFile, + operator.SelectBcfBimSnippetReference, operator.SelectFeaturesDir, operator.SelectDiffJsonFile, operator.SelectDiffNewFile, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 6c94cf4a9a..e7941b2773 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -561,57 +561,81 @@ class LoadBcfTopics(bpy.types.Operator): bcfxml.get_topics() while len(bpy.context.scene.BCFProperties.topics) > 0: bpy.context.scene.BCFProperties.topics.remove(0) - for topic in bcfxml.topics.values(): + index = 0 + for topic_guid in bcfxml.topics.keys(): new = bpy.context.scene.BCFProperties.topics.add() + bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index) + index += 1 + return {"FINISHED"} + + +class LoadBcfTopic(bpy.types.Operator): + bl_idname = "bim.load_bcf_topic" + bl_label = "Load BCF Topics" + topic_guid: bpy.props.StringProperty() + topic_index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = bcfxml.get_topic(self.topic_guid) + new = bpy.context.scene.BCFProperties.topics[self.topic_index] + data_map = { + "name": topic.guid, + "title": topic.title, + "type": topic.topic_type, + "status": topic.topic_status, + "priority": topic.priority, + "stage": topic.stage, + "creation_date": topic.creation_date, + "creation_author": topic.creation_author, + "modified_date": topic.modified_date, + "modified_author": topic.modified_author, + "assigned_to": topic.assigned_to, + "due_date": topic.due_date, + "description": topic.description + } + for key, value in data_map.items(): + if value is not None: + setattr(new, key, str(value)) + while len(new.reference_links) > 0: + new.reference_links.remove(0) + for reference_link in topic.reference_links: + new2 = new.reference_links.add() + new2.name = reference_link + while len(new.labels) > 0: + new.labels.remove(0) + for label in topic.labels: + new2 = new.labels.add() + new2.name = label + if topic.bim_snippet: data_map = { - "name": topic.guid, - "title": topic.title, - "type": topic.topic_type, - "status": topic.topic_status, - "priority": topic.priority, - "stage": topic.stage, - "creation_date": topic.creation_date, - "creation_author": topic.creation_author, - "modified_date": topic.modified_date, - "modified_author": topic.modified_author, - "assigned_to": topic.assigned_to, - "due_date": topic.due_date, - "description": topic.description + "type": topic.bim_snippet.snippet_type, + "is_external": topic.bim_snippet.is_external, + "reference": topic.bim_snippet.reference, + "schema": topic.bim_snippet.reference_schema } for key, value in data_map.items(): if value is not None: - setattr(new, key, str(value)) - for reference_link in topic.reference_links: - new2 = new.reference_links.add() - new2.name = reference_link - for label in topic.labels: - new2 = new.labels.add() - new2.name = label - if topic.bim_snippet: - data_map = { - "type": topic.bim_snippet.snippet_type, - "is_external": topic.bim_snippet.is_external, - "reference": topic.bim_snippet.reference, - "schema": topic.bim_snippet.reference_schema - } - for key, value in data_map.items(): - if value is not None: - setattr(new.bim_snippet, key, value) - for doc in topic.document_references: - new2 = new.document_references.add() - data_map = { - "reference": doc.referenced_document, - "description": doc.description, - "guid": doc.guid, - "is_external": doc.is_external - } - for key, value in data_map.items(): - if value is not None: - setattr(new2, key, value) - for related_topic in topic.related_topics: - new2 = new.related_topics.add() - new2.name = related_topic.guid - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + setattr(new.bim_snippet, key, value) + while len(new.document_references) > 0: + new.document_references.remove(0) + for doc in topic.document_references: + new2 = new.document_references.add() + data_map = { + "reference": doc.referenced_document, + "description": doc.description, + "guid": doc.guid, + "is_external": doc.is_external + } + for key, value in data_map.items(): + if value is not None: + setattr(new2, key, value) + while len(new.related_topics) > 0: + new.related_topics.remove(0) + for related_topic in topic.related_topics: + new2 = new.related_topics.add() + new2.name = related_topic.guid + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) return {"FINISHED"} @@ -730,6 +754,24 @@ class AddBcfTopic(bpy.types.Operator): return {"FINISHED"} +class AddBcfBimSnippet(bpy.types.Operator): + bl_idname = "bim.add_bcf_bim_snippet" + bl_label = "Add BCF BIM Snippet" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bim_snippet = bcf.data.BimSnippet() + bim_snippet.reference = blender_topic.bim_snippet_reference + bim_snippet.reference_schema = blender_topic.bim_snippet_schema + bim_snippet.snippet_type = blender_topic.bim_snippet_type + bcfxml.add_bim_snippet(topic, bim_snippet) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + class AddBcfHeaderFile(bpy.types.Operator): bl_idname = "bim.add_bcf_header_file" bl_label = "Add BCF Header File" @@ -852,10 +894,9 @@ class AddBcfReferenceLink(bpy.types.Operator): topic = bcfxml.topics[blender_topic.name] if not blender_topic.reference_link: return {"FINISHED"} - new = blender_topic.reference_links.add() - new.name = blender_topic.reference_link topic.reference_links.append(blender_topic.reference_link) bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) blender_topic.reference_link = "" return {"FINISHED"} @@ -2454,7 +2495,6 @@ class SelectBcfHeaderFile(bpy.types.Operator): bl_idname = "bim.select_bcf_header_file" bl_label = "Select BCF Header File" filepath: bpy.props.StringProperty(subtype="FILE_PATH") - target: bpy.props.StringProperty() def execute(self, context): if self.filepath: @@ -2468,6 +2508,23 @@ class SelectBcfHeaderFile(bpy.types.Operator): return {"RUNNING_MODAL"} +class SelectBcfBimSnippetReference(bpy.types.Operator): + bl_idname = "bim.select_bcf_bim_snippet_reference" + bl_label = "Select BCF BIM Snippet Reference" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.bim_snippet_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + class SelectFeaturesDir(bpy.types.Operator): bl_idname = "bim.select_features_dir" bl_label = "Select Features Directory" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index ab6520bdd1..3fb972d3c4 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1037,6 +1037,9 @@ class BcfTopic(PropertyGroup): labels: CollectionProperty(name="Labels", type=BcfLabel) label: StringProperty(default="", name="Label") bim_snippet: PointerProperty(type=BcfBimSnippet) + bim_snippet_type: StringProperty(default="", name="Type") + bim_snippet_reference: StringProperty(default="", name="Reference") + bim_snippet_schema: StringProperty(default="", name="Schema") document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) related_topics: CollectionProperty(name="Related Topics", type=StrProperty) comments: CollectionProperty(name="Comments", type=BcfComment) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 7eeef9fbfd..33438b6fbd 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1827,6 +1827,17 @@ class BIM_PT_bcf_metadata(Panel): op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference) row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") + else: + row = layout.row(align=True) + row.prop(topic, "bim_snippet_reference") + row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "bim_snippet_type") + row = layout.row() + row.prop(topic, "bim_snippet_schema") + + row = layout.row() + row.operator("bim.add_bcf_bim_snippet") if topic.document_references: layout.label(text="Document References:") From fa6df3fa3052a1fa32da3fb5289aaa3696df5ab0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 18:44:33 +1100 Subject: [PATCH 60/82] Implement support for deleting document references --- src/bcf/bcf/bcfxml.py | 9 ++++++ .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 15 ++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 30 ++++++++++--------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index c059917e2e..de31c8fa95 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -523,6 +523,15 @@ class BcfXml: topic.bim_snippet = None self.edit_topic(topic) + def delete_document_reference(self, topic, index): + document_reference = topic.document_references[index] + if document_reference.referenced_document and not document_reference.is_external: + filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document) + if os.path.exists(filepath): + os.remove(filepath) + del topic.document_references[index] + self.edit_topic(topic) + def add_bim_snippet(self, topic, bim_snippet): if topic.bim_snippet: self.delete_bim_snippet(topic) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index c8cfc85b86..40bf1aeef9 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -44,6 +44,7 @@ if bpy is not None: operator.RemoveBcfComment, operator.RemoveBcfBimSnippet, operator.RemoveBcfReferenceLink, + operator.RemoveBcfDocumentReference, operator.EditBcfReferenceLinks, operator.EditBcfLabels, operator.RemoveBcfLabel, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index e7941b2773..810c5a4a4c 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1002,6 +1002,21 @@ class RemoveBcfBimSnippet(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.remove_bcf_document_reference" + bl_label = "Remove BCF Document Reference" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_document_reference(topic, self.index) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 33438b6fbd..780b185490 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1839,19 +1839,19 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row() row.operator("bim.add_bcf_bim_snippet") - if topic.document_references: - layout.label(text="Document References:") - for index, doc in enumerate(topic.document_references): - box = self.layout.box() - row = box.row(align=True) - row.prop(doc, "reference") - if doc.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference) - row = box.row(align=True) - row.prop(doc, "description") + layout.label(text="Document References:") + for index, doc in enumerate(topic.document_references): + box = self.layout.box() + row = box.row(align=True) + row.prop(doc, "reference") + if doc.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference) + row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index + row = box.row(align=True) + row.prop(doc, "description") if topic.related_topics: layout.label(text="Related Topics:") @@ -1893,7 +1893,9 @@ class BIM_PT_bcf_comments(Panel): author_text = "*{} ({})".format(comment.modified_author, comment.modified_date) row = box.row(align=True) row.label(text=author_text, icon="WORDWRAP_ON") - row.prop(comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True) + row.prop( + comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True + ) row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name if comment.is_editable: From ea83238e7a6d0c10aab7a2176eb2e59d451e42db Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 19:46:47 +1100 Subject: [PATCH 61/82] Implement support for adding BCF document references --- src/bcf/bcf/bcfxml.py | 13 ++++++ .../blenderbim/bim/__init__.py | 2 + .../blenderbim/bim/operator.py | 42 ++++++++++++++++++- src/ifcblenderexport/blenderbim/bim/prop.py | 2 + src/ifcblenderexport/blenderbim/bim/ui.py | 8 +++- 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index de31c8fa95..08636b01c1 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -532,6 +532,19 @@ class BcfXml: del topic.document_references[index] self.edit_topic(topic) + def add_document_reference(self, topic, document_reference): + if os.path.exists(document_reference.referenced_document): + topic_filepath = os.path.join(self.filepath, topic.guid) + filename = os.path.basename(document_reference.referenced_document) + copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename)) + document_reference.is_external = False + else: + document_reference.is_external = True + if not document_reference.guid: + document_reference.guid = str(uuid.uuid4()) + topic.document_references.append(document_reference) + self.edit_topic(topic) + def add_bim_snippet(self, topic, bim_snippet): if topic.bim_snippet: self.delete_bim_snippet(topic) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 40bf1aeef9..a162b53161 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -39,6 +39,7 @@ if bpy is not None: operator.AddBcfHeaderFile, operator.AddBcfBimSnippet, operator.AddBcfReferenceLink, + operator.AddBcfDocumentReference, operator.AddBcfLabel, operator.ViewBcfTopic, operator.RemoveBcfComment, @@ -58,6 +59,7 @@ if bpy is not None: operator.OpenBcfReferenceLink, operator.SelectBcfHeaderFile, operator.SelectBcfBimSnippetReference, + operator.SelectBcfDocumentReference, operator.SelectFeaturesDir, operator.SelectDiffJsonFile, operator.SelectDiffNewFile, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 810c5a4a4c..f124f29d1d 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -901,6 +901,27 @@ class AddBcfReferenceLink(bpy.types.Operator): return {"FINISHED"} +class AddBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.add_bcf_document_reference" + bl_label = "Add BCF Document Reference" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.document_reference: + return {"FINISHED"} + document_reference = bcf.data.DocumentReference() + document_reference.referenced_document = blender_topic.document_reference + document_reference.description = blender_topic.document_reference_description or None + bcfxml.add_document_reference(topic, document_reference) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + blender_topic.document_reference = "" + blender_topic.document_reference_description = "" + return {"FINISHED"} + + class AddBcfLabel(bpy.types.Operator): bl_idname = "bim.add_bcf_label" bl_label = "Add BCF Label" @@ -946,8 +967,8 @@ class EditBcfLabels(bpy.types.Operator): props = bpy.context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] topic = bcfxml.topics[blender_topic.name] - for index, label in enumerate(topic.labels): - if label == blender_topic.labels[index].name: + for index, label in enumerate(blender_topic.labels): + if label.name == topic.labels[index]: continue topic.labels[index] = blender_topic.labels[index].name bcfxml.edit_topic(topic) @@ -2540,6 +2561,23 @@ class SelectBcfBimSnippetReference(bpy.types.Operator): return {"RUNNING_MODAL"} +class SelectBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.select_bcf_document_reference" + bl_label = "Select BCF Document Reference" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.document_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + class SelectFeaturesDir(bpy.types.Operator): bl_idname = "bim.select_features_dir" bl_label = "Select Features Directory" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 3fb972d3c4..038dce637f 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1041,6 +1041,8 @@ class BcfTopic(PropertyGroup): bim_snippet_reference: StringProperty(default="", name="Reference") bim_snippet_schema: StringProperty(default="", name="Schema") document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) + document_reference: StringProperty(default="", name="Referenced Document") + document_reference_description: StringProperty(default="", name="Description") related_topics: CollectionProperty(name="Related Topics", type=StrProperty) comments: CollectionProperty(name="Comments", type=BcfComment) is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 780b185490..a494382011 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1835,7 +1835,6 @@ class BIM_PT_bcf_metadata(Panel): row.prop(topic, "bim_snippet_type") row = layout.row() row.prop(topic, "bim_snippet_schema") - row = layout.row() row.operator("bim.add_bcf_bim_snippet") @@ -1852,6 +1851,13 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index row = box.row(align=True) row.prop(doc, "description") + row = layout.row(align=True) + row.prop(topic, "document_reference") + row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "document_reference_description") + row = layout.row() + row.operator("bim.add_bcf_document_reference") if topic.related_topics: layout.label(text="Related Topics:") From 37e56c4011a90eb68d217895c5bf726b11cdd6d6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 19:56:57 +1100 Subject: [PATCH 62/82] Implement remove BCF related topic --- src/bcf/bcf/bcfxml.py | 1 + src/ifcblenderexport/blenderbim/bim/__init__.py | 1 + src/ifcblenderexport/blenderbim/bim/operator.py | 16 ++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 10 +++++----- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 08636b01c1..deca2ccdeb 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -537,6 +537,7 @@ class BcfXml: topic_filepath = os.path.join(self.filepath, topic.guid) filename = os.path.basename(document_reference.referenced_document) copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename)) + document_reference.referenced_document = filename document_reference.is_external = False else: document_reference.is_external = True diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index a162b53161..b31b896f26 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -46,6 +46,7 @@ if bpy is not None: operator.RemoveBcfBimSnippet, operator.RemoveBcfReferenceLink, operator.RemoveBcfDocumentReference, + operator.RemoveBcfRelatedTopic, operator.EditBcfReferenceLinks, operator.EditBcfLabels, operator.RemoveBcfLabel, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f124f29d1d..4e629d6f6e 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1038,6 +1038,22 @@ class RemoveBcfDocumentReference(bpy.types.Operator): return {"FINISHED"} +class RemoveBcfRelatedTopic(bpy.types.Operator): + bl_idname = "bim.remove_bcf_related_topic" + bl_label = "Remove BCF Related Topic" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.related_topics[self.index] + bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + class RemoveBcfComment(bpy.types.Operator): bl_idname = "bim.remove_bcf_comment" bl_label = "Remove BCF Comment" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index a494382011..1207ad2969 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1859,11 +1859,11 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row() row.operator("bim.add_bcf_document_reference") - if topic.related_topics: - layout.label(text="Related Topics:") - for related_topic in topic.related_topics: - row = layout.row(align=True) - row.operator("bim.view_bcf_topic", text=related_topic.name).topic_guid = related_topic.name + layout.label(text="Related Topics:") + for index, related_topic in enumerate(topic.related_topics): + row = layout.row(align=True) + row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name + row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index class BIM_PT_bcf_comments(Panel): From c0381588f397fc98dac5f5a5b7bcdf4a15671001 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 28 Dec 2020 20:13:41 +1100 Subject: [PATCH 63/82] Implement add BCF related topic --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 23 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 4 ++++ 4 files changed, 29 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index b31b896f26..898da0642b 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -41,6 +41,7 @@ if bpy is not None: operator.AddBcfReferenceLink, operator.AddBcfDocumentReference, operator.AddBcfLabel, + operator.AddBcfRelatedTopic, operator.ViewBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfBimSnippet, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 4e629d6f6e..cd8e097b0d 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -772,6 +772,29 @@ class AddBcfBimSnippet(bpy.types.Operator): return {"FINISHED"} +class AddBcfRelatedTopic(bpy.types.Operator): + bl_idname = "bim.add_bcf_related_topic" + bl_label = "Add BCF Related Topic" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + related_topic = None + for topic in bcfxml.topics.values(): + if topic.title == blender_topic.related_topic: + related_topic = bcf.data.RelatedTopic() + related_topic.guid = topic.guid + break + if not related_topic: + return {"FINISHED"} + topic = bcfxml.topics[blender_topic.name] + topic.related_topics.append(related_topic) + bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + class AddBcfHeaderFile(bpy.types.Operator): bl_idname = "bim.add_bcf_header_file" bl_label = "Add BCF Header File" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 038dce637f..64d2b61907 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1044,6 +1044,7 @@ class BcfTopic(PropertyGroup): document_reference: StringProperty(default="", name="Referenced Document") document_reference_description: StringProperty(default="", name="Description") related_topics: CollectionProperty(name="Related Topics", type=StrProperty) + related_topic: StringProperty(default="", name="Related Topic") comments: CollectionProperty(name="Comments", type=BcfComment) is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) comment: StringProperty(default="", name="Comment") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 1207ad2969..d00b48ba97 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1864,6 +1864,10 @@ class BIM_PT_bcf_metadata(Panel): row = layout.row(align=True) row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "related_topic") + row = layout.row() + row.operator("bim.add_bcf_related_topic") class BIM_PT_bcf_comments(Panel): From 7f49ebea555ed146a2045075e97a22c39d951512 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 29 Dec 2020 02:04:08 +0700 Subject: [PATCH 64/82] fixed addin grid close #1217, close #1215 --- .../blenderbim/bim/operator.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index cd8e097b0d..e420db8628 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -5525,9 +5525,18 @@ class CopyGrid(bpy.types.Operator): drawing = props.drawings[props.active_drawing_index] collection = bpy.data.collections.get("IfcGroup/" + drawing.name) - for coll in bpy.data.collections: - if coll.name.startswith('IfcGrid'): - for obj in coll.all_objects: - if obj.type == 'MESH': - collection.objects.link(obj.copy()) + existing = [obj for obj in collection.objects if obj.name.startswith('IfcGridAxis')] + for obj in existing: + collection.objects.unlink(obj) + + source = [obj + for coll in bpy.data.collections + if coll.name.startswith('IfcGrid') + for obj in coll.all_objects + if obj.type == 'MESH'] + for obj in source: + dstobj = obj.copy() + dstobj.data = dstobj.data.copy() + collection.objects.link(dstobj) + return {"FINISHED"} From 82468370a70b8ba1e9e1a6680f80848ed5aa851a Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 29 Dec 2020 02:09:48 +0700 Subject: [PATCH 65/82] fixed Level decoration color close #1213 --- src/ifcblenderexport/blenderbim/bim/decoration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 68d1afa855..ead3a4ced5 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -750,7 +750,7 @@ class SectionDecorator(LevelDecorator): if (t > 0.875 || (t > 0.25 && t < 0.375)) { discard; } else { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); + fragColor = COLOR; } } """ From 7c45181a6e9cd83ec787af2cb411f5791534f815 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 29 Dec 2020 02:15:28 +0700 Subject: [PATCH 66/82] fixed decoration of broken geom again --- src/ifcblenderexport/blenderbim/bim/decoration.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index ead3a4ced5..6748501c33 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -403,6 +403,8 @@ class EqualityDecorator(DimensionDecorator): p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 + if dir.length < 1: + continue self.draw_label("EQ", p0 + (dir) * .5, dir) @@ -617,6 +619,8 @@ class LevelDecorator(BaseDecorator): """ for spline in obj.data.splines: spline_points = spline.bezier_points if spline.bezier_points else spline.points + if len(spline_points) < 2: + continue yield [obj.matrix_world @ p.co for p in spline_points] def decorate(self, obj): @@ -691,6 +695,8 @@ class PlanDecorator(LevelDecorator): p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 + if dir.length < 1: + continue text = "RL " + self.format_value(verts[-1].z) self.draw_label(text, p0, dir, gap=8, center=False) @@ -764,5 +770,7 @@ class SectionDecorator(LevelDecorator): p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) dir = p1 - p0 + if dir.length < 1: + continue text = "RL " + self.format_value(verts[-1].z) self.draw_label(text, p0 + dir.normalized() * 16, -dir, gap=16, center=False) From 5c92bd73483aa416ae4e14f56b7d415d89f2df61 Mon Sep 17 00:00:00 2001 From: Long <931924+htlcnn@users.noreply.github.com> Date: Tue, 29 Dec 2020 06:00:49 +0700 Subject: [PATCH 67/82] implement Open Drawing Camera button (#1220) --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 19 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 898da0642b..9f0bcd1334 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -160,6 +160,7 @@ if bpy is not None: operator.AddDrawingToSheet, operator.CreateSheets, operator.OpenView, + operator.OpenViewCamera, operator.ActivateView, operator.ExecuteIfcDiff, operator.VisualiseDiff, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index cd8e097b0d..c2ca58ff9f 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2981,6 +2981,25 @@ class OpenView(bpy.types.Operator): return {"FINISHED"} +class OpenViewCamera(bpy.types.Operator): + bl_idname = "bim.open_view_camera" + bl_label = "Open View Camera" + view_name: bpy.props.StringProperty() + + def execute(self, context): + new_drawing_index = bpy.context.scene.DocProperties.drawings.find(self.view_name) + bpy.context.scene.DocProperties.active_drawing_index = new_drawing_index + drawing = bpy.context.scene.DocProperties.drawings[new_drawing_index] + bpy.context.view_layer.objects.active = drawing.camera + bpy.ops.object.select_all(action="DESELECT") + drawing.camera.select_set(True) + for area in bpy.context.screen.areas: + if area.ui_type == "PROPERTIES": + for space in area.spaces: + space.context = "DATA" + return {"FINISHED"} + + class CutSection(bpy.types.Operator): bl_idname = "bim.cut_section" bl_label = "Cut Section" diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index d00b48ba97..4bb1ff02d1 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2259,6 +2259,17 @@ class BIM_UL_generic(bpy.types.UIList): layout.label(text="", translate=False) +class BIM_UL_drawinglist(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.prop(item, "name", text="", emboss=False) + op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="") + op.view_name = item.name + else: + layout.label(text="", translate=False) + + class BIM_UL_topics(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data @@ -2529,7 +2540,7 @@ class BIM_PT_annotation_utilities(Panel): op = row.operator("bim.open_view", icon="URL", text="") op.view = props.drawings[props.active_drawing_index].name row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index - layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index") + layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index") layout.prop(props, "should_draw_decorations") layout.prop(props, "decorations_colour") From 17e805e1444637092947f137e96802c7f3ea0e11 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 29 Dec 2020 10:03:57 +1100 Subject: [PATCH 68/82] Minor fix - see #1062 --- src/ifcblenderexport/blenderbim/bim/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 9f0bcd1334..d252864ff8 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -365,6 +365,7 @@ if bpy is not None: ui.BIM_PT_clash_manager, ui.BIM_PT_misc_utilities, ui.BIM_UL_generic, + ui.BIM_UL_drawinglist, ui.BIM_UL_clash_sets, ui.BIM_UL_smart_groups, ui.BIM_UL_constraints, From f49174cab4b318e7fb17050083cd90b5b617d8ce Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 29 Dec 2020 13:49:53 +1100 Subject: [PATCH 69/82] Refactor main init to call module inits instead --- .../blenderbim/bim/__init__.py | 55 ++++--------------- .../bim/module/covetool/__init__.py | 21 +++++++ .../blenderbim/bim/module/model/__init__.py | 32 +++++++++++ 3 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/covetool/__init__.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/model/__init__.py diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index d252864ff8..ea4e0ae35a 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -5,21 +5,11 @@ bpy = sys.modules.get("bpy") if bpy is not None: import bpy + import blenderbim.bim.module.covetool + import blenderbim.bim.module.model from . import ui, prop, operator - from .module.covetool import prop as covetool_prop - from .module.covetool import ui as covetool_ui - from .module.covetool import operator as covetool_operator - - from .module.model import grid as model_grid - from .module.model import wall as model_wall - from .module.model import stair as model_stair - from .module.model import door as model_door - from .module.model import window as model_window - from .module.model import slab as model_slab - from .module.model import opening as model_opening - - classes = ( + classes = [ operator.ReassignClass, operator.AssignClass, operator.UnassignClass, @@ -374,22 +364,10 @@ if bpy is not None: ui.BIM_UL_topics, ui.BIM_UL_classifications, ui.BIM_ADDON_preferences, - covetool_prop.CoveToolProject, - covetool_prop.CoveToolSimpleAnalysis, - covetool_prop.CoveToolProperties, - covetool_ui.BIM_UL_covetool_projects, - covetool_ui.BIM_PT_covetool, - covetool_operator.Login, - covetool_operator.RunSimpleAnalysis, - covetool_operator.RunAnalysis, - model_grid.BIM_OT_add_object, - model_wall.BIM_OT_add_object, - model_stair.BIM_OT_add_object, - model_door.BIM_OT_add_object, - model_window.BIM_OT_add_object, - model_slab.BIM_OT_add_object, - model_opening.BIM_OT_add_object, - ) + ] + + classes.extend(module.covetool.classes) + classes.extend(module.model.classes) def menu_func_export(self, context): self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") @@ -421,15 +399,9 @@ if bpy is not None: bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties) bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties) - bpy.types.Scene.CoveToolProperties = bpy.props.PointerProperty(type=covetool_prop.CoveToolProperties) bpy.types.SCENE_PT_unit.append(ui.ifc_units) - bpy.types.VIEW3D_MT_mesh_add.append(model_grid.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_wall.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_stair.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_door.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_window.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_slab.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.append(model_opening.add_object_button) + blenderbim.bim.module.covetool.register() + blenderbim.bim.module.model.register() bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler) def unregister(): @@ -451,11 +423,6 @@ if bpy is not None: del bpy.types.Camera.BIMCameraProperties del bpy.types.TextCurve.BIMTextProperties bpy.types.SCENE_PT_unit.remove(ui.ifc_units) - bpy.types.VIEW3D_MT_mesh_add.remove(model_grid.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_wall.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_stair.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_door.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_window.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_slab.add_object_button) - bpy.types.VIEW3D_MT_mesh_add.remove(model_opening.add_object_button) + blenderbim.bim.module.model.unregister() + blenderbim.bim.module.covetool.unregister() bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler) diff --git a/src/ifcblenderexport/blenderbim/bim/module/covetool/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/covetool/__init__.py new file mode 100644 index 0000000000..0113965696 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/covetool/__init__.py @@ -0,0 +1,21 @@ +import bpy +from . import ui, prop, operator + +classes = ( + prop.CoveToolProject, + prop.CoveToolSimpleAnalysis, + prop.CoveToolProperties, + ui.BIM_UL_covetool_projects, + ui.BIM_PT_covetool, + operator.Login, + operator.RunSimpleAnalysis, + operator.RunAnalysis, +) + + +def register(): + bpy.types.Scene.CoveToolProperties = bpy.props.PointerProperty(type=prop.CoveToolProperties) + + +def unregister(): + del bpy.types.Scene.CoveToolProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/model/__init__.py new file mode 100644 index 0000000000..09c610553b --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/model/__init__.py @@ -0,0 +1,32 @@ +import bpy +from . import grid, wall, stair, door, window, slab, opening + +classes = ( + grid.BIM_OT_add_object, + wall.BIM_OT_add_object, + stair.BIM_OT_add_object, + door.BIM_OT_add_object, + window.BIM_OT_add_object, + slab.BIM_OT_add_object, + opening.BIM_OT_add_object, +) + + +def register(): + bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(wall.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(slab.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button) + + +def unregister(): + bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(wall.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(slab.add_object_button) + bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button) From ceb7b0e13f6feb54b89093ef658f177f27811c97 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 29 Dec 2020 14:15:15 +1100 Subject: [PATCH 70/82] Refactor BCF features into its own module --- .../blenderbim/bim/__init__.py | 68 +- .../blenderbim/bim/module/bcf/__init__.py | 59 ++ .../bim/{ => module/bcf}/bcfstore.py | 0 .../blenderbim/bim/module/bcf/operator.py | 868 ++++++++++++++++++ .../blenderbim/bim/module/bcf/prop.py | 150 +++ .../blenderbim/bim/module/bcf/ui.py | 283 ++++++ .../blenderbim/bim/operator.py | 868 ------------------ src/ifcblenderexport/blenderbim/bim/prop.py | 135 --- src/ifcblenderexport/blenderbim/bim/ui.py | 281 ------ 9 files changed, 1372 insertions(+), 1340 deletions(-) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/bcf/__init__.py rename src/ifcblenderexport/blenderbim/bim/{ => module/bcf}/bcfstore.py (100%) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index ea4e0ae35a..2d7ab5bc4e 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -5,8 +5,9 @@ bpy = sys.modules.get("bpy") if bpy is not None: import bpy - import blenderbim.bim.module.covetool - import blenderbim.bim.module.model + import blenderbim.bim.module.covetool as module_covetool + import blenderbim.bim.module.model as module_model + import blenderbim.bim.module.bcf as module_bcf from . import ui, prop, operator classes = [ @@ -15,43 +16,7 @@ if bpy is not None: operator.UnassignClass, operator.SelectClass, operator.SelectType, - operator.NewBcfProject, - operator.LoadBcfProject, - operator.LoadBcfTopics, - operator.LoadBcfTopic, - operator.LoadBcfComments, - operator.EditBcfProjectName, - operator.EditBcfAuthor, - operator.EditBcfTopicName, - operator.EditBcfTopic, - operator.SaveBcfProject, - operator.AddBcfTopic, - operator.AddBcfHeaderFile, - operator.AddBcfBimSnippet, - operator.AddBcfReferenceLink, - operator.AddBcfDocumentReference, - operator.AddBcfLabel, - operator.AddBcfRelatedTopic, - operator.ViewBcfTopic, - operator.RemoveBcfComment, - operator.RemoveBcfBimSnippet, - operator.RemoveBcfReferenceLink, - operator.RemoveBcfDocumentReference, - operator.RemoveBcfRelatedTopic, - operator.EditBcfReferenceLinks, - operator.EditBcfLabels, - operator.RemoveBcfLabel, - operator.EditBcfComment, - operator.AddBcfComment, - operator.ActivateBcfViewpoint, - operator.AddBcfViewpoint, - operator.RemoveBcfViewpoint, - operator.RemoveBcfFile, operator.OpenUri, - operator.OpenBcfReferenceLink, - operator.SelectBcfHeaderFile, - operator.SelectBcfBimSnippetReference, - operator.SelectBcfDocumentReference, operator.SelectFeaturesDir, operator.SelectDiffJsonFile, operator.SelectDiffNewFile, @@ -255,8 +220,6 @@ if bpy is not None: operator.SnapSpacesTogether, operator.CopyGrid, prop.StrProperty, - prop.BcfReferenceLink, - prop.BcfLabel, prop.Attribute, prop.MaterialLayer, prop.MaterialConstituent, @@ -282,15 +245,10 @@ if bpy is not None: prop.Schedule, prop.DrawingStyle, prop.Sheet, - prop.BcfBimSnippet, - prop.BcfDocumentReference, - prop.BcfComment, - prop.BcfTopic, prop.Subcontext, prop.PresentationLayer, prop.BIMProperties, prop.BIMDebugProperties, - prop.BCFProperties, prop.DocProperties, prop.BIMLibrary, prop.MapConversion, @@ -319,9 +277,6 @@ if bpy is not None: ui.BIM_PT_search, ui.BIM_PT_ifccsv, ui.BIM_PT_ifcclash, - ui.BIM_PT_bcf, - ui.BIM_PT_bcf_metadata, - ui.BIM_PT_bcf_comments, ui.BIM_PT_owner, ui.BIM_PT_people, ui.BIM_PT_organisations, @@ -366,8 +321,9 @@ if bpy is not None: ui.BIM_ADDON_preferences, ] - classes.extend(module.covetool.classes) - classes.extend(module.model.classes) + classes.extend(module_bcf.classes) + classes.extend(module_covetool.classes) + classes.extend(module_model.classes) def menu_func_export(self, context): self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") @@ -388,7 +344,6 @@ if bpy is not None: bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties) - bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary) bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion) @@ -400,8 +355,9 @@ if bpy is not None: bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties) bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties) bpy.types.SCENE_PT_unit.append(ui.ifc_units) - blenderbim.bim.module.covetool.register() - blenderbim.bim.module.model.register() + module_bcf.register() + module_covetool.register() + module_model.register() bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler) def unregister(): @@ -412,7 +368,6 @@ if bpy is not None: bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del bpy.types.Scene.BIMProperties del bpy.types.Scene.BIMDebugProperties - del bpy.types.Scene.BCFProperties del bpy.types.Scene.DocProperties del bpy.types.Scene.MapConversion del bpy.types.Scene.TargetCRS @@ -423,6 +378,7 @@ if bpy is not None: del bpy.types.Camera.BIMCameraProperties del bpy.types.TextCurve.BIMTextProperties bpy.types.SCENE_PT_unit.remove(ui.ifc_units) - blenderbim.bim.module.model.unregister() - blenderbim.bim.module.covetool.unregister() + module_model.unregister() + module_covetool.unregister() + module_bcf.unregister() bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler) diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/__init__.py new file mode 100644 index 0000000000..ab9af36d50 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/__init__.py @@ -0,0 +1,59 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.NewBcfProject, + operator.LoadBcfProject, + operator.LoadBcfTopics, + operator.LoadBcfTopic, + operator.LoadBcfComments, + operator.EditBcfProjectName, + operator.EditBcfAuthor, + operator.EditBcfTopicName, + operator.EditBcfTopic, + operator.SaveBcfProject, + operator.AddBcfTopic, + operator.AddBcfHeaderFile, + operator.AddBcfBimSnippet, + operator.AddBcfReferenceLink, + operator.AddBcfDocumentReference, + operator.AddBcfLabel, + operator.AddBcfRelatedTopic, + operator.ViewBcfTopic, + operator.RemoveBcfComment, + operator.RemoveBcfBimSnippet, + operator.RemoveBcfReferenceLink, + operator.RemoveBcfDocumentReference, + operator.RemoveBcfRelatedTopic, + operator.EditBcfReferenceLinks, + operator.EditBcfLabels, + operator.RemoveBcfLabel, + operator.EditBcfComment, + operator.AddBcfComment, + operator.ActivateBcfViewpoint, + operator.AddBcfViewpoint, + operator.RemoveBcfViewpoint, + operator.RemoveBcfFile, + operator.OpenBcfReferenceLink, + operator.SelectBcfHeaderFile, + operator.SelectBcfBimSnippetReference, + operator.SelectBcfDocumentReference, + prop.BcfReferenceLink, + prop.BcfLabel, + prop.BcfBimSnippet, + prop.BcfDocumentReference, + prop.BcfComment, + prop.BcfTopic, + prop.BCFProperties, + ui.BIM_PT_bcf, + ui.BIM_PT_bcf_metadata, + ui.BIM_PT_bcf_comments, +) + + +def register(): + bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties) + + +def unregister(): + del bpy.types.Scene.BCFProperties diff --git a/src/ifcblenderexport/blenderbim/bim/bcfstore.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/bcfstore.py similarity index 100% rename from src/ifcblenderexport/blenderbim/bim/bcfstore.py rename to src/ifcblenderexport/blenderbim/bim/module/bcf/bcfstore.py diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py new file mode 100644 index 0000000000..e953023915 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py @@ -0,0 +1,868 @@ +import os +import bpy +import bcf +from . import bcfstore + +class NewBcfProject(bpy.types.Operator): + bl_idname = "bim.new_bcf_project" + bl_label = "New BCF Project" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.new_project() + bpy.ops.bim.load_bcf_project() + return {"FINISHED"} + + +class LoadBcfProject(bpy.types.Operator): + bl_idname = "bim.load_bcf_project" + bl_label = "Load BCF Project" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + if self.filepath: + bcfxml.get_project(self.filepath) + bpy.context.scene.BCFProperties.name = bcfxml.project.name + bpy.ops.bim.load_bcf_topics() + bpy.context.scene.BCFProperties.is_loaded = True + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class LoadBcfTopics(bpy.types.Operator): + bl_idname = "bim.load_bcf_topics" + bl_label = "Load BCF Topics" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.get_topics() + while len(bpy.context.scene.BCFProperties.topics) > 0: + bpy.context.scene.BCFProperties.topics.remove(0) + index = 0 + for topic_guid in bcfxml.topics.keys(): + new = bpy.context.scene.BCFProperties.topics.add() + bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index) + index += 1 + return {"FINISHED"} + + +class LoadBcfTopic(bpy.types.Operator): + bl_idname = "bim.load_bcf_topic" + bl_label = "Load BCF Topics" + topic_guid: bpy.props.StringProperty() + topic_index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = bcfxml.get_topic(self.topic_guid) + new = bpy.context.scene.BCFProperties.topics[self.topic_index] + data_map = { + "name": topic.guid, + "title": topic.title, + "type": topic.topic_type, + "status": topic.topic_status, + "priority": topic.priority, + "stage": topic.stage, + "creation_date": topic.creation_date, + "creation_author": topic.creation_author, + "modified_date": topic.modified_date, + "modified_author": topic.modified_author, + "assigned_to": topic.assigned_to, + "due_date": topic.due_date, + "description": topic.description + } + for key, value in data_map.items(): + if value is not None: + setattr(new, key, str(value)) + while len(new.reference_links) > 0: + new.reference_links.remove(0) + for reference_link in topic.reference_links: + new2 = new.reference_links.add() + new2.name = reference_link + while len(new.labels) > 0: + new.labels.remove(0) + for label in topic.labels: + new2 = new.labels.add() + new2.name = label + if topic.bim_snippet: + data_map = { + "type": topic.bim_snippet.snippet_type, + "is_external": topic.bim_snippet.is_external, + "reference": topic.bim_snippet.reference, + "schema": topic.bim_snippet.reference_schema + } + for key, value in data_map.items(): + if value is not None: + setattr(new.bim_snippet, key, value) + while len(new.document_references) > 0: + new.document_references.remove(0) + for doc in topic.document_references: + new2 = new.document_references.add() + data_map = { + "reference": doc.referenced_document, + "description": doc.description, + "guid": doc.guid, + "is_external": doc.is_external + } + for key, value in data_map.items(): + if value is not None: + setattr(new2, key, value) + while len(new.related_topics) > 0: + new.related_topics.remove(0) + for related_topic in topic.related_topics: + new2 = new.related_topics.add() + new2.name = related_topic.guid + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + return {"FINISHED"} + + +class LoadBcfComments(bpy.types.Operator): + bl_idname = "bim.load_bcf_comments" + bl_label = "Load BCF Comments" + topic_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.get_comments(self.topic_guid) + blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid) + while len(blender_topic.comments) > 0: + blender_topic.comments.remove(0) + for comment in bcfxml.topics[self.topic_guid].comments.values(): + new = blender_topic.comments.add() + data_map = { + "name": comment.guid, + "comment": comment.comment, + "viewpoint": comment.viewpoint.guid if comment.viewpoint else None, + "date": comment.date, + "author": comment.author, + "modified_date": comment.modified_date, + "modified_author": comment.modified_author, + } + for key, value in data_map.items(): + if value is not None: + setattr(new, key, str(value)) + return {"FINISHED"} + + +class EditBcfProjectName(bpy.types.Operator): + bl_idname = "bim.edit_bcf_project_name" + bl_label = "Edit BCF Project Name" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.project.name = bpy.context.scene.BCFProperties.name + bcfxml.edit_project() + return {"FINISHED"} + + +class EditBcfAuthor(bpy.types.Operator): + bl_idname = "bim.edit_bcf_author" + bl_label = "Edit BCF Author" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.author = bpy.context.scene.BCFProperties.author + return {"FINISHED"} + + +class EditBcfTopicName(bpy.types.Operator): + bl_idname = "bim.edit_bcf_topic_name" + bl_label = "Edit BCF Topic Name" + + def execute(self, context): + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = bcfxml.topics[blender_topic.name] + topic.title = blender_topic.title + bcfxml.edit_topic(topic) + return {"FINISHED"} + + +class EditBcfTopic(bpy.types.Operator): + bl_idname = "bim.edit_bcf_topic" + bl_label = "Edit BCF Topic" + + def execute(self, context): + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + bcfxml = bcfstore.BcfStore.get_bcfxml() + + topic = bcfxml.topics[blender_topic.name] + topic.title = blender_topic.title or None + topic.priority = blender_topic.priority or None + topic.due_date = blender_topic.due_date or None + topic.assigned_to = blender_topic.assigned_to or None + topic.stage = blender_topic.stage or None + topic.description = blender_topic.description or None + topic.topic_status = blender_topic.status or None + topic.topic_type = blender_topic.type or None + + bcfxml.edit_topic(topic) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + +class SaveBcfProject(bpy.types.Operator): + bl_idname = "bim.save_bcf_project" + bl_label = "Save BCF Project" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.save_project(self.filepath) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class AddBcfTopic(bpy.types.Operator): + bl_idname = "bim.add_bcf_topic" + bl_label = "Add BCF Topic" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.add_topic() + new = bpy.context.scene.BCFProperties.topics.add() + new.name = "New Topic" + bpy.ops.bim.load_bcf_topics() + return {"FINISHED"} + + +class AddBcfBimSnippet(bpy.types.Operator): + bl_idname = "bim.add_bcf_bim_snippet" + bl_label = "Add BCF BIM Snippet" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bim_snippet = bcf.data.BimSnippet() + bim_snippet.reference = blender_topic.bim_snippet_reference + bim_snippet.reference_schema = blender_topic.bim_snippet_schema + bim_snippet.snippet_type = blender_topic.bim_snippet_type + bcfxml.add_bim_snippet(topic, bim_snippet) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + +class AddBcfRelatedTopic(bpy.types.Operator): + bl_idname = "bim.add_bcf_related_topic" + bl_label = "Add BCF Related Topic" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + related_topic = None + for topic in bcfxml.topics.values(): + if topic.title == blender_topic.related_topic: + related_topic = bcf.data.RelatedTopic() + related_topic.guid = topic.guid + break + if not related_topic: + return {"FINISHED"} + topic = bcfxml.topics[blender_topic.name] + topic.related_topics.append(related_topic) + bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + +class AddBcfHeaderFile(bpy.types.Operator): + bl_idname = "bim.add_bcf_header_file" + bl_label = "Add BCF Header File" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + header_file = bcf.data.HeaderFile() + header_file.reference = blender_topic.file_reference + if not os.path.exists(header_file.reference): + header_file.filename = header_file.reference + if len(blender_topic.file_ifc_project) == 22: + header_file.ifc_project = blender_topic.file_ifc_project + if len(blender_topic.file_ifc_spatial_structure_element) == 22: + header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element + bcfxml.add_file(topic, header_file) + props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + return {"FINISHED"} + + +class ViewBcfTopic(bpy.types.Operator): + bl_idname = "bim.view_bcf_topic" + bl_label = "Get BCF Topic" + topic_guid: bpy.props.StringProperty() + + def execute(self, context): + for index, topic in enumerate(bpy.context.scene.BCFProperties.topics): + if topic.guid.lower() == self.topic_guid.lower(): + bpy.context.scene.BCFProperties.active_topic_index = index + return {"FINISHED"} + + +class AddBcfViewpoint(bpy.types.Operator): + bl_idname = "bim.add_bcf_viewpoint" + bl_label = "Add BCF Viewpoint" + + def execute(self, context): + if not bpy.context.scene.camera: + return {"FINISHED"} + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + viewpoint = bcf.data.Viewpoint() + + if bpy.context.scene.camera.data.type == "ORTHO": + camera = bcf.data.OrthogonalCamera() + camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale + viewpoint.orthogonal_camera = camera + elif bpy.context.scene.camera.data.type == "PERSP": + camera = bcf.data.PerspectiveCamera() + camera.field_of_view = degrees(bpy.context.scene.camera.data.angle) + viewpoint.perspective_camera = camera + camera.camera_view_point.x = bpy.context.scene.camera.location.x + camera.camera_view_point.y = bpy.context.scene.camera.location.y + camera.camera_view_point.z = bpy.context.scene.camera.location.z + direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) + camera.camera_direction.x = direction.x + camera.camera_direction.y = direction.y + camera.camera_direction.z = direction.z + up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) + camera.camera_up_vector.x = up.x + camera.camera_up_vector.y = up.y + camera.camera_up_vector.z = up.z + + old_file_format = bpy.context.scene.render.image_settings.file_format + bpy.context.scene.render.image_settings.file_format = "PNG" + old_filepath = bpy.context.scene.render.filepath + bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png") + bpy.ops.render.opengl(write_still=True) + viewpoint.snapshot = bpy.context.scene.render.filepath + + bcfxml.add_viewpoint(topic, viewpoint) + bpy.context.scene.render.filepath = old_filepath + bpy.context.scene.render.image_settings.file_format = old_file_format + props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + return {"FINISHED"} + + +class RemoveBcfViewpoint(bpy.types.Operator): + bl_idname = "bim.remove_bcf_viewpoint" + bl_label = "Remove BCF Viewpoint" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + viewpoint_guid = blender_topic.viewpoints + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_viewpoint(viewpoint_guid, topic) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + +class RemoveBcfFile(bpy.types.Operator): + bl_idname = "bim.remove_bcf_file" + bl_label = "Remove BCF File" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_file(topic, self.index) + props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + return {"FINISHED"} + + +class AddBcfReferenceLink(bpy.types.Operator): + bl_idname = "bim.add_bcf_reference_link" + bl_label = "Add BCF Reference Link" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.reference_link: + return {"FINISHED"} + topic.reference_links.append(blender_topic.reference_link) + bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + blender_topic.reference_link = "" + return {"FINISHED"} + + +class AddBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.add_bcf_document_reference" + bl_label = "Add BCF Document Reference" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.document_reference: + return {"FINISHED"} + document_reference = bcf.data.DocumentReference() + document_reference.referenced_document = blender_topic.document_reference + document_reference.description = blender_topic.document_reference_description or None + bcfxml.add_document_reference(topic, document_reference) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + blender_topic.document_reference = "" + blender_topic.document_reference_description = "" + return {"FINISHED"} + + +class AddBcfLabel(bpy.types.Operator): + bl_idname = "bim.add_bcf_label" + bl_label = "Add BCF Label" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.label: + return {"FINISHED"} + new = blender_topic.labels.add() + new.name = blender_topic.label + topic.labels.append(blender_topic.label) + bcfxml.edit_topic(topic) + blender_topic.label = "" + return {"FINISHED"} + + +class EditBcfReferenceLinks(bpy.types.Operator): + bl_idname = "bim.edit_bcf_reference_links" + bl_label = "Edit BCF Reference Link" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + for index, reference_link in enumerate(topic.reference_links): + if reference_link == blender_topic.reference_links[index].name: + continue + topic.reference_links[index] = blender_topic.reference_links[index].name + bcfxml.edit_topic(topic) + return {"FINISHED"} + + +class EditBcfLabels(bpy.types.Operator): + bl_idname = "bim.edit_bcf_labels" + bl_label = "Edit BCF Labels" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + for index, label in enumerate(blender_topic.labels): + if label.name == topic.labels[index]: + continue + topic.labels[index] = blender_topic.labels[index].name + bcfxml.edit_topic(topic) + return {"FINISHED"} + + +class RemoveBcfReferenceLink(bpy.types.Operator): + bl_idname = "bim.remove_bcf_reference_link" + bl_label = "Remove BCF Reference Link" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.reference_links[self.index] + bcfxml.edit_topic(topic) + blender_topic.reference_links.remove(self.index) + return {"FINISHED"} + + +class RemoveBcfLabel(bpy.types.Operator): + bl_idname = "bim.remove_bcf_label" + bl_label = "Remove BCF Label" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.labels[self.index] + bcfxml.edit_topic(topic) + blender_topic.labels.remove(self.index) + return {"FINISHED"} + + +class RemoveBcfBimSnippet(bpy.types.Operator): + bl_idname = "bim.remove_bcf_bim_snippet" + bl_label = "Remove BCF BIM Snippet" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_bim_snippet(topic) + blender_topic.bim_snippet.schema = "" + blender_topic.bim_snippet.reference = "" + blender_topic.bim_snippet.type = "" + return {"FINISHED"} + + +class RemoveBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.remove_bcf_document_reference" + bl_label = "Remove BCF Document Reference" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_document_reference(topic, self.index) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + +class RemoveBcfRelatedTopic(bpy.types.Operator): + bl_idname = "bim.remove_bcf_related_topic" + bl_label = "Remove BCF Related Topic" + index: bpy.props.IntProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + del topic.related_topics[self.index] + bcfxml.edit_topic(topic) + bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + return {"FINISHED"} + + +class RemoveBcfComment(bpy.types.Operator): + bl_idname = "bim.remove_bcf_comment" + bl_label = "Remove BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + bcfxml.delete_comment(self.comment_guid, topic) + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + return {"FINISHED"} + + +class EditBcfComment(bpy.types.Operator): + bl_idname = "bim.edit_bcf_comment" + bl_label = "Edit BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + blender_comment = blender_topic.comments.get(self.comment_guid) + topic = bcfxml.topics[blender_topic.name] + comment = topic.comments[self.comment_guid] + comment.comment = blender_comment.comment + bcfxml.edit_comment(comment, topic) + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + return {"FINISHED"} + + +class AddBcfComment(bpy.types.Operator): + bl_idname = "bim.add_bcf_comment" + bl_label = "Add BCF Comment" + comment_guid: bpy.props.StringProperty() + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not blender_topic.comment: + return {"FINISHED"} + comment = bcf.data.Comment() + comment.comment = blender_topic.comment + if blender_topic.has_related_viewpoint and blender_topic.viewpoints: + comment.viewpoint = bcf.data.Viewpoint() + comment.viewpoint.guid = blender_topic.viewpoints + bcfxml.add_comment(topic, comment) + bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + print(bcfxml.filepath) + return {"FINISHED"} + + +class ActivateBcfViewpoint(bpy.types.Operator): + bl_idname = "bim.activate_bcf_viewpoint" + bl_label = "Activate BCF Viewpoint" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.name] + if not topic.viewpoints: + return {"FINISHED"} + + viewpoint_guid = blender_topic.viewpoints + viewpoint = topic.viewpoints[viewpoint_guid] + obj = bpy.data.objects.get("Viewpoint") + if not obj: + obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint")) + bpy.context.scene.collection.objects.link(obj) + bpy.context.scene.camera = obj + + cam_width = bpy.context.scene.render.resolution_x + cam_height = bpy.context.scene.render.resolution_y + cam_aspect = cam_width / cam_height + + if viewpoint.snapshot: + obj.data.show_background_images = True + while len(obj.data.background_images) > 0: + obj.data.background_images.remove(obj.data.background_images[0]) + background = obj.data.background_images.new() + background.image = bpy.data.images.load( + os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot) + ) + src_width = background.image.size[0] + src_height = background.image.size[1] + src_aspect = src_width / src_height + + if src_aspect > cam_aspect: + background.frame_method = "FIT" + else: + background.frame_method = "CROP" + background.display_depth = "FRONT" + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" + + if viewpoint.orthogonal_camera: + camera = viewpoint.orthogonal_camera + obj.data.type = "ORTHO" + obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale + elif viewpoint.perspective_camera: + camera = viewpoint.perspective_camera + obj.data.type = "PERSP" + if cam_aspect >= 1: + obj.data.angle = radians(camera.field_of_view) + else: + # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov + obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))) + + self.set_viewpoint_components(viewpoint) + + gp = bpy.data.grease_pencils.get("BCF") + if gp: + bpy.data.grease_pencils.remove(gp) + if viewpoint.lines: + self.draw_lines(viewpoint) + + self.delete_clipping_planes() + if viewpoint.clipping_planes: + self.create_clipping_planes(viewpoint) + + self.delete_bitmaps() + if viewpoint.bitmaps: + self.create_bitmaps(bcfxml, viewpoint, topic) + + z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized() + y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized() + x_axis = y_axis.cross(z_axis).normalized() + rotation = Matrix((x_axis, y_axis, z_axis)) + rotation.invert() + location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z)) + obj.matrix_world = rotation.to_4x4() + obj.location = location + return {"FINISHED"} + + def set_viewpoint_components(self, viewpoint): + if not viewpoint.components: + return + selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection] + exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions] + global_id_colours = {} + for coloring in viewpoint.components.coloring: + for component in coloring.components: + global_id_colours.setdefault(component.ifc_guid, coloring.color) + + for obj in bpy.data.objects: + global_id = obj.BIMObjectProperties.attributes.get("GlobalId") + if not global_id: + continue + global_id = global_id.string_value + is_visible = viewpoint.components.visibility.default_visibility + if global_id in exception_global_ids: + is_visible = not is_visible + if not is_visible: + obj.hide_set(True) + continue + if "IfcSpace" in obj.name: + is_visible = viewpoint.components.viewSetuphints.spacesVisible + elif "IfcOpeningElement" in obj.name: + is_visible = viewpoint.components.viewSetuphints.openingsVisible + obj.hide_set(not is_visible) + if not is_visible: + continue + obj.select_set(global_id in selected_global_ids) + if global_id in global_id_colours: + obj.color = self.hex_to_rgb(global_id_colours[global_id]) + + def draw_lines(self, viewpoint): + gp = bpy.data.grease_pencils.new("BCF") + scene = bpy.context.scene + scene.grease_pencil = gp + scene.frame_set(1) + layer = gp.layers.new("BCF Annotation", set_active=True) + layer.thickness = 3 + layer.color = (1, 0, 0) + frame = layer.frames.new(1) + stroke = frame.strokes.new() + stroke.display_mode = "3DSPACE" + stroke.points.add(len(viewpoint.lines) * 2) + coords = [] + for l in viewpoint.lines: + coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]) + stroke.points.foreach_set("co", coords) + + def create_clipping_planes(self, viewpoint): + n = 0 + for plane in viewpoint.clipping_planes: + bpy.ops.bim.add_section_plane() + if n == 0: + obj = bpy.data.objects["Section"] + else: + obj = bpy.data.objects["Section.{:03d}".format(n)] + obj.location = (plane.location.x, plane.location.y, plane.location.z) + obj.rotation_mode = "QUATERNION" + obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat( + "Z", "Y" + ) + n += 1 + + def delete_clipping_planes(self): + collection = bpy.data.collections.get("Sections") + if not collection: + return + for section in collection.objects: + bpy.context.view_layer.objects.active = section + bpy.ops.bim.remove_section_plane() + + def delete_bitmaps(self): + collection = bpy.data.collections.get("Bitmaps") + if not collection: + collection = bpy.data.collections.new("Bitmaps") + bpy.context.scene.collection.children.link(collection) + for bitmap in collection.objects: + bpy.data.objects.remove(bitmap) + + def create_bitmaps(self, bcfxml, viewpoint, topic): + collection = bpy.data.collections.get("Bitmaps") + if not collection: + collection = bpy.data.collections.new("Bitmaps") + for bitmap in viewpoint.bitmaps: + obj = bpy.data.objects.new("Bitmap", None) + obj.empty_display_type = "IMAGE" + image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference)) + src_width = image.size[0] + src_height = image.size[1] + if src_height > src_width: + obj.empty_display_size = bitmap.height + else: + obj.empty_display_size = bitmap.height * (src_width / src_height) + obj.data = image + y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z)) + z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z)) + x = y.cross(z) + obj.matrix_world = Matrix( + [[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]] + ) + obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z) + collection.objects.link(obj) + + def hex_to_rgb(self, value): + value = value.lstrip("#") + lv = len(value) + t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) + return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1] + +class OpenBcfReferenceLink(bpy.types.Operator): + bl_idname = "bim.open_bcf_reference_link" + bl_label = "Open BCF Reference Link" + index: bpy.props.IntProperty() + + def execute(self, context): + webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name) + return {"FINISHED"} + + +class SelectBcfHeaderFile(bpy.types.Operator): + bl_idname = "bim.select_bcf_header_file" + bl_label = "Select BCF Header File" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.file_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class SelectBcfBimSnippetReference(bpy.types.Operator): + bl_idname = "bim.select_bcf_bim_snippet_reference" + bl_label = "Select BCF BIM Snippet Reference" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.bim_snippet_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class SelectBcfDocumentReference(bpy.types.Operator): + bl_idname = "bim.select_bcf_document_reference" + bl_label = "Select BCF Document Reference" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + if self.filepath: + props = bpy.context.scene.BCFProperties + topic = props.topics[props.active_topic_index] + topic.document_reference = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py new file mode 100644 index 0000000000..8401a069f5 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py @@ -0,0 +1,150 @@ +import bpy +from . import bcfstore +from blenderbim.bim.prop import StrProperty +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + + +bcfviewpoints_enum = None + + +def updateBcfReferenceLink(self, context): + bpy.ops.bim.edit_bcf_reference_links() + + +def updateBcfLabel(self, context): + bpy.ops.bim.edit_bcf_labels() + + +def updateBcfProjectName(self, context): + bpy.ops.bim.edit_bcf_project_name() + + +def updateBcfAuthor(self, context): + bpy.ops.bim.edit_bcf_author() + + +def updateBcfTopicName(self, context): + bpy.ops.bim.edit_bcf_topic_name() + + +def updateBcfTopicIsEditable(self, context): + if not self.is_editable: + bpy.ops.bim.edit_bcf_topic() + + +def updateBcfCommentIsEditable(self, context): + if not self.is_editable: + bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) + + +def refreshBcfTopic(self, context): + global bcfviewpoints_enum + bcfviewpoints_enum = None + + props = bpy.context.scene.BCFProperties + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = props.topics[props.active_topic_index] + header = bcfxml.get_header(topic.name) + getBcfViewpoints(self, context) + + +class BcfReferenceLink(PropertyGroup): + name: StringProperty(name="Name", update=updateBcfReferenceLink) + + +class BcfLabel(PropertyGroup): + name: StringProperty(name="Name", update=updateBcfLabel) + + +def getBcfViewpoints(self, context): + global bcfviewpoints_enum + if bcfviewpoints_enum is None: + bcfviewpoints_enum = [] + props = bpy.context.scene.BCFProperties + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = props.topics[props.active_topic_index] + viewpoints = bcfxml.get_viewpoints(topic.name) + bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) + return bcfviewpoints_enum + + +class BcfBimSnippet(PropertyGroup): + schema: StringProperty(name="Schema") + reference: StringProperty(name="Reference") + type: StringProperty(name="Type") + is_external: BoolProperty(name="Is External") + + +class BcfDocumentReference(PropertyGroup): + reference: StringProperty(name="Reference") + description: StringProperty(name="Description") + guid: StringProperty(name="GUID") + is_external: BoolProperty(name="Is External") + + +class BcfComment(PropertyGroup): + name: StringProperty(name="GUID") + date: StringProperty(name="Date") + author: StringProperty(name="Author") + comment: StringProperty(name="Comment") + viewpoint: StringProperty(name="Viewpoint") + modified_date: StringProperty(name="Modified Date") + modified_author: StringProperty(name="Modified Author") + is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable) + + +class BcfTopic(PropertyGroup): + name: StringProperty(name="GUID") + title: StringProperty(default="", name="Title", update=updateBcfTopicName) + type: StringProperty(default="", name="Type") + status: StringProperty(default="", name="Status") + priority: StringProperty(default="", name="Priority") + stage: StringProperty(default="", name="Stage") + creation_date: StringProperty(default="", name="Date") + creation_author: StringProperty(default="", name="Author") + modified_date: StringProperty(default="", name="Modified Date") + modified_author: StringProperty(default="", name="Modified By") + assigned_to: StringProperty(default="", name="Assigned To") + due_date: StringProperty(default="", name="Due Date") + description: StringProperty(default="", name="Description") + viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") + files: CollectionProperty(name="Files", type=StrProperty) + file_reference: StringProperty(default="", name="Reference") + file_ifc_project: StringProperty(default="", name="IFC Project") + file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") + reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) + reference_link: StringProperty(default="", name="Reference Link") + labels: CollectionProperty(name="Labels", type=BcfLabel) + label: StringProperty(default="", name="Label") + bim_snippet: PointerProperty(type=BcfBimSnippet) + bim_snippet_type: StringProperty(default="", name="Type") + bim_snippet_reference: StringProperty(default="", name="Reference") + bim_snippet_schema: StringProperty(default="", name="Schema") + document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) + document_reference: StringProperty(default="", name="Referenced Document") + document_reference_description: StringProperty(default="", name="Description") + related_topics: CollectionProperty(name="Related Topics", type=StrProperty) + related_topic: StringProperty(default="", name="Related Topic") + comments: CollectionProperty(name="Comments", type=BcfComment) + is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) + comment: StringProperty(default="", name="Comment") + has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) + + +class BCFProperties(PropertyGroup): + is_loaded: BoolProperty(name="Is Loaded", default=False) + comment_text_width: IntProperty(name="Comment Text Width", default=40) + name: StringProperty(default="", name="Project Name", update=updateBcfProjectName) + author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor) + topics: CollectionProperty(name="BCF Topics", type=BcfTopic) + active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic) diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py new file mode 100644 index 0000000000..f2d22c08ee --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py @@ -0,0 +1,283 @@ +import os +import bpy +from . import bcfstore +from bpy.types import Panel + +class BIM_PT_bcf(Panel): + bl_label = "BIM Collaboration Format (BCF)" + bl_idname = "BIM_PT_bcf" + 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 + layout.use_property_decorate = False + + scene = context.scene + props = bpy.context.scene.BCFProperties + + row = layout.row(align=True) + row.operator("bim.new_bcf_project", text="New Project") + row.operator("bim.load_bcf_project", text="Load Project") + + if not props.is_loaded: + return + + row.operator("bim.save_bcf_project", text="Save Project") + + row = layout.row() + row.prop(props, "name") + + row = layout.row() + row.prop(props, "author") + + props = bpy.context.scene.BCFProperties + row = layout.row() + row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index") + col = row.column(align=True) + col.operator("bim.add_bcf_topic", icon="ADD", text="") + if props.active_topic_index < len(props.topics): + topic = props.topics[props.active_topic_index] + col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True) + + if props.active_topic_index < len(props.topics): + topic = props.topics[props.active_topic_index] + row = layout.row() + row.enabled = topic.is_editable + row.prop(topic, "description", text="") + + row = layout.row() + row.prop(topic, "viewpoints") + row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") + row.operator("bim.add_bcf_viewpoint", icon="ADD", text="") + row.operator("bim.remove_bcf_viewpoint", icon="X", text="") + + col = layout.column(align=True) + if topic.type: + col.prop(topic, "type", emboss=topic.is_editable) + if topic.status: + col.prop(topic, "status", emboss=topic.is_editable) + if topic.priority: + col.prop(topic, "priority", emboss=topic.is_editable) + if topic.stage: + col.prop(topic, "stage", emboss=topic.is_editable) + if topic.assigned_to: + col.prop(topic, "assigned_to", emboss=topic.is_editable) + if topic.due_date: + col.prop(topic, "due_date", emboss=topic.is_editable) + + col = layout.column(align=True) + if topic.modified_date: + col.prop(topic, "modified_date", emboss=False) + col.prop(topic, "modified_author", emboss=False) + else: + col.prop(topic, "creation_date", emboss=False) + col.prop(topic, "creation_author", emboss=False) + + +class BIM_PT_bcf_metadata(Panel): + bl_label = "BCF Metadata" + bl_idname = "BIM_PT_bcf_metadata" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_bcf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + scene = context.scene + props = bpy.context.scene.BCFProperties + + if props.active_topic_index >= len(props.topics): + layout.label(text="No BCF project is loaded") + return + + topic = props.topics[props.active_topic_index] + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcf_topic = bcfxml.topics[topic.name] + + layout.label(text="Header Files:") + + if bcf_topic.header: + for index, f in enumerate(bcf_topic.header.files): + box = self.layout.box() + row = box.row(align=True) + row.label(text=f.filename, icon="FILE_BLANK") + if f.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = f.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) + row.operator("bim.remove_bcf_file", icon="X", text="").index = index + + row = box.row() + row.label(text="Date") + row.label(text=f.date) + + if f.ifc_project: + row = box.row() + row.label(text="IFC Project") + row.label(text=f.ifc_project) + + if f.ifc_spatial_structure_element: + row = box.row() + row.label(text="IFC Spatial Structure Element") + row.label(text=f.ifc_spatial_structure_element) + + row = layout.row(align=True) + row.prop(topic, "file_reference") + row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "file_ifc_project") + row = layout.row() + row.prop(topic, "file_ifc_spatial_structure_element") + + row = layout.row() + row.operator("bim.add_bcf_header_file") + + layout.label(text="Reference Links:") + for index, link in enumerate(topic.reference_links): + row = layout.row(align=True) + row.prop(link, "name") + row.operator("bim.open_uri", icon="URL", text="").uri = link.name + row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "reference_link") + row = layout.row() + row.operator("bim.add_bcf_reference_link") + + layout.label(text="Labels:") + for index, label in enumerate(topic.labels): + row = layout.row(align=True) + row.prop(label, "name", text="") + row.operator("bim.remove_bcf_label", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "label") + row = layout.row() + row.operator("bim.add_bcf_label") + + layout.label(text="BIM Snippet:") + if topic.bim_snippet.schema: + row = layout.row(align=True) + row.prop(topic.bim_snippet, "type", emboss=False) + if topic.bim_snippet.schema: + row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema + + row = layout.row(align=True) + row.prop(topic.bim_snippet, "reference", emboss=False) + if topic.bim_snippet.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference) + row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") + else: + row = layout.row(align=True) + row.prop(topic, "bim_snippet_reference") + row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "bim_snippet_type") + row = layout.row() + row.prop(topic, "bim_snippet_schema") + row = layout.row() + row.operator("bim.add_bcf_bim_snippet") + + layout.label(text="Document References:") + for index, doc in enumerate(topic.document_references): + box = self.layout.box() + row = box.row(align=True) + row.prop(doc, "reference") + if doc.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference) + row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index + row = box.row(align=True) + row.prop(doc, "description") + row = layout.row(align=True) + row.prop(topic, "document_reference") + row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="") + row = layout.row() + row.prop(topic, "document_reference_description") + row = layout.row() + row.operator("bim.add_bcf_document_reference") + + layout.label(text="Related Topics:") + for index, related_topic in enumerate(topic.related_topics): + row = layout.row(align=True) + row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name + row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + row = layout.row() + row.prop(topic, "related_topic") + row = layout.row() + row.operator("bim.add_bcf_related_topic") + + +class BIM_PT_bcf_comments(Panel): + bl_label = "BCF Comments" + bl_idname = "BIM_PT_bcf_comments" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_bcf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + scene = context.scene + props = bpy.context.scene.BCFProperties + + if props.active_topic_index >= len(props.topics): + layout.label(text="No BCF project is loaded") + return + + row = layout.row() + row.prop(props, "comment_text_width") + + topic = props.topics[props.active_topic_index] + for comment in topic.comments: + box = self.layout.box() + + author_text = "{} ({})".format(comment.author, comment.date) + if comment.modified_author: + author_text = "*{} ({})".format(comment.modified_author, comment.modified_date) + row = box.row(align=True) + row.label(text=author_text, icon="WORDWRAP_ON") + row.prop( + comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True + ) + row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name + + if comment.is_editable: + row = box.row() + row.prop(comment, "comment", text="") + else: + col = box.column(align=True) + col.scale_y = 0.8 + words = comment.comment.split() + while words: + total_line_chars = 0 + line_words = [] + while words and total_line_chars < props.comment_text_width: + word = words.pop(0) + line_words.append(word) + total_line_chars += len(word) + 1 # 1 is for the space + col.label(text=" ".join(line_words)) + + row = layout.row() + row.prop(topic, "comment") + row = layout.row() + row.prop(topic, "has_related_viewpoint") + row = layout.row() + row.operator("bim.add_bcf_comment") diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index c2ca58ff9f..2073e496df 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1,7 +1,6 @@ from itertools import chain import os import bpy -import bcf import uuid import time import json @@ -22,7 +21,6 @@ from . import svgwriter from . import sheeter from . import scheduler from . import schema -from . import bcfstore from . import ifc from . import annotation from . import helper @@ -522,811 +520,6 @@ class RejectElement(bpy.types.Operator): return {"FINISHED"} -class NewBcfProject(bpy.types.Operator): - bl_idname = "bim.new_bcf_project" - bl_label = "New BCF Project" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.new_project() - bpy.ops.bim.load_bcf_project() - return {"FINISHED"} - - -class LoadBcfProject(bpy.types.Operator): - bl_idname = "bim.load_bcf_project" - bl_label = "Load BCF Project" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - if self.filepath: - bcfxml.get_project(self.filepath) - bpy.context.scene.BCFProperties.name = bcfxml.project.name - bpy.ops.bim.load_bcf_topics() - bpy.context.scene.BCFProperties.is_loaded = True - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class LoadBcfTopics(bpy.types.Operator): - bl_idname = "bim.load_bcf_topics" - bl_label = "Load BCF Topics" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.get_topics() - while len(bpy.context.scene.BCFProperties.topics) > 0: - bpy.context.scene.BCFProperties.topics.remove(0) - index = 0 - for topic_guid in bcfxml.topics.keys(): - new = bpy.context.scene.BCFProperties.topics.add() - bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index) - index += 1 - return {"FINISHED"} - - -class LoadBcfTopic(bpy.types.Operator): - bl_idname = "bim.load_bcf_topic" - bl_label = "Load BCF Topics" - topic_guid: bpy.props.StringProperty() - topic_index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = bcfxml.get_topic(self.topic_guid) - new = bpy.context.scene.BCFProperties.topics[self.topic_index] - data_map = { - "name": topic.guid, - "title": topic.title, - "type": topic.topic_type, - "status": topic.topic_status, - "priority": topic.priority, - "stage": topic.stage, - "creation_date": topic.creation_date, - "creation_author": topic.creation_author, - "modified_date": topic.modified_date, - "modified_author": topic.modified_author, - "assigned_to": topic.assigned_to, - "due_date": topic.due_date, - "description": topic.description - } - for key, value in data_map.items(): - if value is not None: - setattr(new, key, str(value)) - while len(new.reference_links) > 0: - new.reference_links.remove(0) - for reference_link in topic.reference_links: - new2 = new.reference_links.add() - new2.name = reference_link - while len(new.labels) > 0: - new.labels.remove(0) - for label in topic.labels: - new2 = new.labels.add() - new2.name = label - if topic.bim_snippet: - data_map = { - "type": topic.bim_snippet.snippet_type, - "is_external": topic.bim_snippet.is_external, - "reference": topic.bim_snippet.reference, - "schema": topic.bim_snippet.reference_schema - } - for key, value in data_map.items(): - if value is not None: - setattr(new.bim_snippet, key, value) - while len(new.document_references) > 0: - new.document_references.remove(0) - for doc in topic.document_references: - new2 = new.document_references.add() - data_map = { - "reference": doc.referenced_document, - "description": doc.description, - "guid": doc.guid, - "is_external": doc.is_external - } - for key, value in data_map.items(): - if value is not None: - setattr(new2, key, value) - while len(new.related_topics) > 0: - new.related_topics.remove(0) - for related_topic in topic.related_topics: - new2 = new.related_topics.add() - new2.name = related_topic.guid - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) - return {"FINISHED"} - - -class LoadBcfComments(bpy.types.Operator): - bl_idname = "bim.load_bcf_comments" - bl_label = "Load BCF Comments" - topic_guid: bpy.props.StringProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.get_comments(self.topic_guid) - blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid) - while len(blender_topic.comments) > 0: - blender_topic.comments.remove(0) - for comment in bcfxml.topics[self.topic_guid].comments.values(): - new = blender_topic.comments.add() - data_map = { - "name": comment.guid, - "comment": comment.comment, - "viewpoint": comment.viewpoint.guid if comment.viewpoint else None, - "date": comment.date, - "author": comment.author, - "modified_date": comment.modified_date, - "modified_author": comment.modified_author, - } - for key, value in data_map.items(): - if value is not None: - setattr(new, key, str(value)) - return {"FINISHED"} - - -class EditBcfProjectName(bpy.types.Operator): - bl_idname = "bim.edit_bcf_project_name" - bl_label = "Edit BCF Project Name" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.project.name = bpy.context.scene.BCFProperties.name - bcfxml.edit_project() - return {"FINISHED"} - - -class EditBcfAuthor(bpy.types.Operator): - bl_idname = "bim.edit_bcf_author" - bl_label = "Edit BCF Author" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.author = bpy.context.scene.BCFProperties.author - return {"FINISHED"} - - -class EditBcfTopicName(bpy.types.Operator): - bl_idname = "bim.edit_bcf_topic_name" - bl_label = "Edit BCF Topic Name" - - def execute(self, context): - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = bcfxml.topics[blender_topic.name] - topic.title = blender_topic.title - bcfxml.edit_topic(topic) - return {"FINISHED"} - - -class EditBcfTopic(bpy.types.Operator): - bl_idname = "bim.edit_bcf_topic" - bl_label = "Edit BCF Topic" - - def execute(self, context): - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - bcfxml = bcfstore.BcfStore.get_bcfxml() - - topic = bcfxml.topics[blender_topic.name] - topic.title = blender_topic.title or None - topic.priority = blender_topic.priority or None - topic.due_date = blender_topic.due_date or None - topic.assigned_to = blender_topic.assigned_to or None - topic.stage = blender_topic.stage or None - topic.description = blender_topic.description or None - topic.topic_status = blender_topic.status or None - topic.topic_type = blender_topic.type or None - - bcfxml.edit_topic(topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic - return {"FINISHED"} - - -class SaveBcfProject(bpy.types.Operator): - bl_idname = "bim.save_bcf_project" - bl_label = "Save BCF Project" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.save_project(self.filepath) - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class AddBcfTopic(bpy.types.Operator): - bl_idname = "bim.add_bcf_topic" - bl_label = "Add BCF Topic" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcfxml.add_topic() - new = bpy.context.scene.BCFProperties.topics.add() - new.name = "New Topic" - bpy.ops.bim.load_bcf_topics() - return {"FINISHED"} - - -class AddBcfBimSnippet(bpy.types.Operator): - bl_idname = "bim.add_bcf_bim_snippet" - bl_label = "Add BCF BIM Snippet" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - bim_snippet = bcf.data.BimSnippet() - bim_snippet.reference = blender_topic.bim_snippet_reference - bim_snippet.reference_schema = blender_topic.bim_snippet_schema - bim_snippet.snippet_type = blender_topic.bim_snippet_type - bcfxml.add_bim_snippet(topic, bim_snippet) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - return {"FINISHED"} - - -class AddBcfRelatedTopic(bpy.types.Operator): - bl_idname = "bim.add_bcf_related_topic" - bl_label = "Add BCF Related Topic" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - related_topic = None - for topic in bcfxml.topics.values(): - if topic.title == blender_topic.related_topic: - related_topic = bcf.data.RelatedTopic() - related_topic.guid = topic.guid - break - if not related_topic: - return {"FINISHED"} - topic = bcfxml.topics[blender_topic.name] - topic.related_topics.append(related_topic) - bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - return {"FINISHED"} - - -class AddBcfHeaderFile(bpy.types.Operator): - bl_idname = "bim.add_bcf_header_file" - bl_label = "Add BCF Header File" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - header_file = bcf.data.HeaderFile() - header_file.reference = blender_topic.file_reference - if not os.path.exists(header_file.reference): - header_file.filename = header_file.reference - if len(blender_topic.file_ifc_project) == 22: - header_file.ifc_project = blender_topic.file_ifc_project - if len(blender_topic.file_ifc_spatial_structure_element) == 22: - header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element - bcfxml.add_file(topic, header_file) - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic - return {"FINISHED"} - - -class ViewBcfTopic(bpy.types.Operator): - bl_idname = "bim.view_bcf_topic" - bl_label = "Get BCF Topic" - topic_guid: bpy.props.StringProperty() - - def execute(self, context): - for index, topic in enumerate(bpy.context.scene.BCFProperties.topics): - if topic.guid.lower() == self.topic_guid.lower(): - bpy.context.scene.BCFProperties.active_topic_index = index - return {"FINISHED"} - - -class AddBcfViewpoint(bpy.types.Operator): - bl_idname = "bim.add_bcf_viewpoint" - bl_label = "Add BCF Viewpoint" - - def execute(self, context): - if not bpy.context.scene.camera: - return {"FINISHED"} - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - viewpoint = bcf.data.Viewpoint() - - if bpy.context.scene.camera.data.type == "ORTHO": - camera = bcf.data.OrthogonalCamera() - camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale - viewpoint.orthogonal_camera = camera - elif bpy.context.scene.camera.data.type == "PERSP": - camera = bcf.data.PerspectiveCamera() - camera.field_of_view = degrees(bpy.context.scene.camera.data.angle) - viewpoint.perspective_camera = camera - camera.camera_view_point.x = bpy.context.scene.camera.location.x - camera.camera_view_point.y = bpy.context.scene.camera.location.y - camera.camera_view_point.z = bpy.context.scene.camera.location.z - direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) - camera.camera_direction.x = direction.x - camera.camera_direction.y = direction.y - camera.camera_direction.z = direction.z - up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) - camera.camera_up_vector.x = up.x - camera.camera_up_vector.y = up.y - camera.camera_up_vector.z = up.z - - old_file_format = bpy.context.scene.render.image_settings.file_format - bpy.context.scene.render.image_settings.file_format = "PNG" - old_filepath = bpy.context.scene.render.filepath - bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png") - bpy.ops.render.opengl(write_still=True) - viewpoint.snapshot = bpy.context.scene.render.filepath - - bcfxml.add_viewpoint(topic, viewpoint) - bpy.context.scene.render.filepath = old_filepath - bpy.context.scene.render.image_settings.file_format = old_file_format - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic - return {"FINISHED"} - - -class RemoveBcfViewpoint(bpy.types.Operator): - bl_idname = "bim.remove_bcf_viewpoint" - bl_label = "Remove BCF Viewpoint" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - viewpoint_guid = blender_topic.viewpoints - topic = bcfxml.topics[blender_topic.name] - bcfxml.delete_viewpoint(viewpoint_guid, topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic - return {"FINISHED"} - - -class RemoveBcfFile(bpy.types.Operator): - bl_idname = "bim.remove_bcf_file" - bl_label = "Remove BCF File" - index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - bcfxml.delete_file(topic, self.index) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic - return {"FINISHED"} - - -class AddBcfReferenceLink(bpy.types.Operator): - bl_idname = "bim.add_bcf_reference_link" - bl_label = "Add BCF Reference Link" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - if not blender_topic.reference_link: - return {"FINISHED"} - topic.reference_links.append(blender_topic.reference_link) - bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - blender_topic.reference_link = "" - return {"FINISHED"} - - -class AddBcfDocumentReference(bpy.types.Operator): - bl_idname = "bim.add_bcf_document_reference" - bl_label = "Add BCF Document Reference" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - if not blender_topic.document_reference: - return {"FINISHED"} - document_reference = bcf.data.DocumentReference() - document_reference.referenced_document = blender_topic.document_reference - document_reference.description = blender_topic.document_reference_description or None - bcfxml.add_document_reference(topic, document_reference) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - blender_topic.document_reference = "" - blender_topic.document_reference_description = "" - return {"FINISHED"} - - -class AddBcfLabel(bpy.types.Operator): - bl_idname = "bim.add_bcf_label" - bl_label = "Add BCF Label" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - if not blender_topic.label: - return {"FINISHED"} - new = blender_topic.labels.add() - new.name = blender_topic.label - topic.labels.append(blender_topic.label) - bcfxml.edit_topic(topic) - blender_topic.label = "" - return {"FINISHED"} - - -class EditBcfReferenceLinks(bpy.types.Operator): - bl_idname = "bim.edit_bcf_reference_links" - bl_label = "Edit BCF Reference Link" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - for index, reference_link in enumerate(topic.reference_links): - if reference_link == blender_topic.reference_links[index].name: - continue - topic.reference_links[index] = blender_topic.reference_links[index].name - bcfxml.edit_topic(topic) - return {"FINISHED"} - - -class EditBcfLabels(bpy.types.Operator): - bl_idname = "bim.edit_bcf_labels" - bl_label = "Edit BCF Labels" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - for index, label in enumerate(blender_topic.labels): - if label.name == topic.labels[index]: - continue - topic.labels[index] = blender_topic.labels[index].name - bcfxml.edit_topic(topic) - return {"FINISHED"} - - -class RemoveBcfReferenceLink(bpy.types.Operator): - bl_idname = "bim.remove_bcf_reference_link" - bl_label = "Remove BCF Reference Link" - index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - del topic.reference_links[self.index] - bcfxml.edit_topic(topic) - blender_topic.reference_links.remove(self.index) - return {"FINISHED"} - - -class RemoveBcfLabel(bpy.types.Operator): - bl_idname = "bim.remove_bcf_label" - bl_label = "Remove BCF Label" - index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - del topic.labels[self.index] - bcfxml.edit_topic(topic) - blender_topic.labels.remove(self.index) - return {"FINISHED"} - - -class RemoveBcfBimSnippet(bpy.types.Operator): - bl_idname = "bim.remove_bcf_bim_snippet" - bl_label = "Remove BCF BIM Snippet" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - bcfxml.delete_bim_snippet(topic) - blender_topic.bim_snippet.schema = "" - blender_topic.bim_snippet.reference = "" - blender_topic.bim_snippet.type = "" - return {"FINISHED"} - - -class RemoveBcfDocumentReference(bpy.types.Operator): - bl_idname = "bim.remove_bcf_document_reference" - bl_label = "Remove BCF Document Reference" - index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - bcfxml.delete_document_reference(topic, self.index) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - return {"FINISHED"} - - -class RemoveBcfRelatedTopic(bpy.types.Operator): - bl_idname = "bim.remove_bcf_related_topic" - bl_label = "Remove BCF Related Topic" - index: bpy.props.IntProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - del topic.related_topics[self.index] - bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) - return {"FINISHED"} - - -class RemoveBcfComment(bpy.types.Operator): - bl_idname = "bim.remove_bcf_comment" - bl_label = "Remove BCF Comment" - comment_guid: bpy.props.StringProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - bcfxml.delete_comment(self.comment_guid, topic) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) - return {"FINISHED"} - - -class EditBcfComment(bpy.types.Operator): - bl_idname = "bim.edit_bcf_comment" - bl_label = "Edit BCF Comment" - comment_guid: bpy.props.StringProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - blender_comment = blender_topic.comments.get(self.comment_guid) - topic = bcfxml.topics[blender_topic.name] - comment = topic.comments[self.comment_guid] - comment.comment = blender_comment.comment - bcfxml.edit_comment(comment, topic) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) - return {"FINISHED"} - - -class AddBcfComment(bpy.types.Operator): - bl_idname = "bim.add_bcf_comment" - bl_label = "Add BCF Comment" - comment_guid: bpy.props.StringProperty() - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - if not blender_topic.comment: - return {"FINISHED"} - comment = bcf.data.Comment() - comment.comment = blender_topic.comment - if blender_topic.has_related_viewpoint and blender_topic.viewpoints: - comment.viewpoint = bcf.data.Viewpoint() - comment.viewpoint.guid = blender_topic.viewpoints - bcfxml.add_comment(topic, comment) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) - print(bcfxml.filepath) - return {"FINISHED"} - - -class ActivateBcfViewpoint(bpy.types.Operator): - bl_idname = "bim.activate_bcf_viewpoint" - bl_label = "Activate BCF Viewpoint" - - def execute(self, context): - bcfxml = bcfstore.BcfStore.get_bcfxml() - props = bpy.context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] - topic = bcfxml.topics[blender_topic.name] - if not topic.viewpoints: - return {"FINISHED"} - - viewpoint_guid = blender_topic.viewpoints - viewpoint = topic.viewpoints[viewpoint_guid] - obj = bpy.data.objects.get("Viewpoint") - if not obj: - obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint")) - bpy.context.scene.collection.objects.link(obj) - bpy.context.scene.camera = obj - - cam_width = bpy.context.scene.render.resolution_x - cam_height = bpy.context.scene.render.resolution_y - cam_aspect = cam_width / cam_height - - if viewpoint.snapshot: - obj.data.show_background_images = True - while len(obj.data.background_images) > 0: - obj.data.background_images.remove(obj.data.background_images[0]) - background = obj.data.background_images.new() - background.image = bpy.data.images.load( - os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot) - ) - src_width = background.image.size[0] - src_height = background.image.size[1] - src_aspect = src_width / src_height - - if src_aspect > cam_aspect: - background.frame_method = "FIT" - else: - background.frame_method = "CROP" - background.display_depth = "FRONT" - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].region_3d.view_perspective = "CAMERA" - - if viewpoint.orthogonal_camera: - camera = viewpoint.orthogonal_camera - obj.data.type = "ORTHO" - obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale - elif viewpoint.perspective_camera: - camera = viewpoint.perspective_camera - obj.data.type = "PERSP" - if cam_aspect >= 1: - obj.data.angle = radians(camera.field_of_view) - else: - # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov - obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))) - - self.set_viewpoint_components(viewpoint) - - gp = bpy.data.grease_pencils.get("BCF") - if gp: - bpy.data.grease_pencils.remove(gp) - if viewpoint.lines: - self.draw_lines(viewpoint) - - self.delete_clipping_planes() - if viewpoint.clipping_planes: - self.create_clipping_planes(viewpoint) - - self.delete_bitmaps() - if viewpoint.bitmaps: - self.create_bitmaps(bcfxml, viewpoint, topic) - - z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized() - y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized() - x_axis = y_axis.cross(z_axis).normalized() - rotation = Matrix((x_axis, y_axis, z_axis)) - rotation.invert() - location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z)) - obj.matrix_world = rotation.to_4x4() - obj.location = location - return {"FINISHED"} - - def set_viewpoint_components(self, viewpoint): - if not viewpoint.components: - return - selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection] - exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions] - global_id_colours = {} - for coloring in viewpoint.components.coloring: - for component in coloring.components: - global_id_colours.setdefault(component.ifc_guid, coloring.color) - - for obj in bpy.data.objects: - global_id = obj.BIMObjectProperties.attributes.get("GlobalId") - if not global_id: - continue - global_id = global_id.string_value - is_visible = viewpoint.components.visibility.default_visibility - if global_id in exception_global_ids: - is_visible = not is_visible - if not is_visible: - obj.hide_set(True) - continue - if "IfcSpace" in obj.name: - is_visible = viewpoint.components.viewSetuphints.spacesVisible - elif "IfcOpeningElement" in obj.name: - is_visible = viewpoint.components.viewSetuphints.openingsVisible - obj.hide_set(not is_visible) - if not is_visible: - continue - obj.select_set(global_id in selected_global_ids) - if global_id in global_id_colours: - obj.color = self.hex_to_rgb(global_id_colours[global_id]) - - def draw_lines(self, viewpoint): - gp = bpy.data.grease_pencils.new("BCF") - scene = bpy.context.scene - scene.grease_pencil = gp - scene.frame_set(1) - layer = gp.layers.new("BCF Annotation", set_active=True) - layer.thickness = 3 - layer.color = (1, 0, 0) - frame = layer.frames.new(1) - stroke = frame.strokes.new() - stroke.display_mode = "3DSPACE" - stroke.points.add(len(viewpoint.lines) * 2) - coords = [] - for l in viewpoint.lines: - coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]) - stroke.points.foreach_set("co", coords) - - def create_clipping_planes(self, viewpoint): - n = 0 - for plane in viewpoint.clipping_planes: - bpy.ops.bim.add_section_plane() - if n == 0: - obj = bpy.data.objects["Section"] - else: - obj = bpy.data.objects["Section.{:03d}".format(n)] - obj.location = (plane.location.x, plane.location.y, plane.location.z) - obj.rotation_mode = "QUATERNION" - obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat( - "Z", "Y" - ) - n += 1 - - def delete_clipping_planes(self): - collection = bpy.data.collections.get("Sections") - if not collection: - return - for section in collection.objects: - bpy.context.view_layer.objects.active = section - bpy.ops.bim.remove_section_plane() - - def delete_bitmaps(self): - collection = bpy.data.collections.get("Bitmaps") - if not collection: - collection = bpy.data.collections.new("Bitmaps") - bpy.context.scene.collection.children.link(collection) - for bitmap in collection.objects: - bpy.data.objects.remove(bitmap) - - def create_bitmaps(self, bcfxml, viewpoint, topic): - collection = bpy.data.collections.get("Bitmaps") - if not collection: - collection = bpy.data.collections.new("Bitmaps") - for bitmap in viewpoint.bitmaps: - obj = bpy.data.objects.new("Bitmap", None) - obj.empty_display_type = "IMAGE" - image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference)) - src_width = image.size[0] - src_height = image.size[1] - if src_height > src_width: - obj.empty_display_size = bitmap.height - else: - obj.empty_display_size = bitmap.height * (src_width / src_height) - obj.data = image - y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z)) - z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z)) - x = y.cross(z) - obj.matrix_world = Matrix( - [[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]] - ) - obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z) - collection.objects.link(obj) - - def hex_to_rgb(self, value): - value = value.lstrip("#") - lv = len(value) - t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) - return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1] - - class OpenUri(bpy.types.Operator): bl_idname = "bim.open_uri" bl_label = "Open URI" @@ -1337,16 +530,6 @@ class OpenUri(bpy.types.Operator): return {"FINISHED"} -class OpenBcfReferenceLink(bpy.types.Operator): - bl_idname = "bim.open_bcf_reference_link" - bl_label = "Open BCF Reference Link" - index: bpy.props.IntProperty() - - def execute(self, context): - webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name) - return {"FINISHED"} - - class SelectAudited(bpy.types.Operator): bl_idname = "bim.select_audited" bl_label = "Select Audited" @@ -2566,57 +1749,6 @@ class SelectSmartGroup(bpy.types.Operator): return {"FINISHED"} -class SelectBcfHeaderFile(bpy.types.Operator): - bl_idname = "bim.select_bcf_header_file" - bl_label = "Select BCF Header File" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - if self.filepath: - props = bpy.context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.file_reference = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class SelectBcfBimSnippetReference(bpy.types.Operator): - bl_idname = "bim.select_bcf_bim_snippet_reference" - bl_label = "Select BCF BIM Snippet Reference" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - if self.filepath: - props = bpy.context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.bim_snippet_reference = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class SelectBcfDocumentReference(bpy.types.Operator): - bl_idname = "bim.select_bcf_document_reference" - bl_label = "Select BCF Document Reference" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - if self.filepath: - props = bpy.context.scene.BCFProperties - topic = props.topics[props.active_topic_index] - topic.document_reference = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - class SelectFeaturesDir(bpy.types.Operator): bl_idname = "bim.select_features_dir" bl_label = "Select Features Directory" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 64d2b61907..2f5e19e6f2 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -5,7 +5,6 @@ import ifcopenshell.util.pset from pathlib import Path from . import export_ifc from . import schema -from . import bcfstore from . import ifc from . import annotation from . import decoration @@ -52,7 +51,6 @@ persons_enum = [] organisations_enum = [] sheets_enum = [] vector_styles_enum = [] -bcfviewpoints_enum = None @persistent @@ -167,14 +165,6 @@ def refreshPredefinedTypes(self, context): context.scene.BIMProperties.ifc_predefined_type = enum[0][0] -def updateBcfReferenceLink(self, context): - bpy.ops.bim.edit_bcf_reference_links() - - -def updateBcfLabel(self, context): - bpy.ops.bim.edit_bcf_labels() - - def getDiagramScales(self, context): global diagram_scales_enum if ( @@ -562,47 +552,6 @@ def refreshFontSize(self, context): annotation.Annotator.resize_text(context.active_object) -def updateBcfProjectName(self, context): - bpy.ops.bim.edit_bcf_project_name() - - -def updateBcfAuthor(self, context): - bpy.ops.bim.edit_bcf_author() - - -def updateBcfTopicName(self, context): - bpy.ops.bim.edit_bcf_topic_name() - - -def updateBcfTopicIsEditable(self, context): - if not self.is_editable: - bpy.ops.bim.edit_bcf_topic() - - -def updateBcfCommentIsEditable(self, context): - if not self.is_editable: - bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) - - -def refreshBcfTopic(self, context): - global bcfviewpoints_enum - bcfviewpoints_enum = None - - props = bpy.context.scene.BCFProperties - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] - header = bcfxml.get_header(topic.name) - getBcfViewpoints(self, context) - - -class BcfReferenceLink(PropertyGroup): - name: StringProperty(name="Name", update=updateBcfReferenceLink) - - -class BcfLabel(PropertyGroup): - name: StringProperty(name="Name", update=updateBcfLabel) - - class StrProperty(PropertyGroup): pass @@ -976,81 +925,6 @@ class Constraint(PropertyGroup): user_defined_qualifier: StringProperty(name="Custom Qualifier") -def getBcfViewpoints(self, context): - global bcfviewpoints_enum - if bcfviewpoints_enum is None: - bcfviewpoints_enum = [] - props = bpy.context.scene.BCFProperties - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] - viewpoints = bcfxml.get_viewpoints(topic.name) - bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) - return bcfviewpoints_enum - - -class BcfBimSnippet(PropertyGroup): - schema: StringProperty(name="Schema") - reference: StringProperty(name="Reference") - type: StringProperty(name="Type") - is_external: BoolProperty(name="Is External") - - -class BcfDocumentReference(PropertyGroup): - reference: StringProperty(name="Reference") - description: StringProperty(name="Description") - guid: StringProperty(name="GUID") - is_external: BoolProperty(name="Is External") - - -class BcfComment(PropertyGroup): - name: StringProperty(name="GUID") - date: StringProperty(name="Date") - author: StringProperty(name="Author") - comment: StringProperty(name="Comment") - viewpoint: StringProperty(name="Viewpoint") - modified_date: StringProperty(name="Modified Date") - modified_author: StringProperty(name="Modified Author") - is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable) - - -class BcfTopic(PropertyGroup): - name: StringProperty(name="GUID") - title: StringProperty(default="", name="Title", update=updateBcfTopicName) - type: StringProperty(default="", name="Type") - status: StringProperty(default="", name="Status") - priority: StringProperty(default="", name="Priority") - stage: StringProperty(default="", name="Stage") - creation_date: StringProperty(default="", name="Date") - creation_author: StringProperty(default="", name="Author") - modified_date: StringProperty(default="", name="Modified Date") - modified_author: StringProperty(default="", name="Modified By") - assigned_to: StringProperty(default="", name="Assigned To") - due_date: StringProperty(default="", name="Due Date") - description: StringProperty(default="", name="Description") - viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") - files: CollectionProperty(name="Files", type=StrProperty) - file_reference: StringProperty(default="", name="Reference") - file_ifc_project: StringProperty(default="", name="IFC Project") - file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element") - reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) - reference_link: StringProperty(default="", name="Reference Link") - labels: CollectionProperty(name="Labels", type=BcfLabel) - label: StringProperty(default="", name="Label") - bim_snippet: PointerProperty(type=BcfBimSnippet) - bim_snippet_type: StringProperty(default="", name="Type") - bim_snippet_reference: StringProperty(default="", name="Reference") - bim_snippet_schema: StringProperty(default="", name="Schema") - document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) - document_reference: StringProperty(default="", name="Referenced Document") - document_reference_description: StringProperty(default="", name="Description") - related_topics: CollectionProperty(name="Related Topics", type=StrProperty) - related_topic: StringProperty(default="", name="Related Topic") - comments: CollectionProperty(name="Comments", type=BcfComment) - is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable) - comment: StringProperty(default="", name="Comment") - has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) - - class PropertySetTemplate(PropertyGroup): global_id: StringProperty(name="Global ID") name: StringProperty(name="Name") @@ -1562,15 +1436,6 @@ class BIMProperties(PropertyGroup): presentation_layers: CollectionProperty(name="Presentation Layers", type=PresentationLayer) -class BCFProperties(PropertyGroup): - is_loaded: BoolProperty(name="Is Loaded", default=False) - comment_text_width: IntProperty(name="Comment Text Width", default=40) - name: StringProperty(default="", name="Project Name", update=updateBcfProjectName) - author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor) - topics: CollectionProperty(name="BCF Topics", type=BcfTopic) - active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic) - - class MapConversion(PropertyGroup): eastings: StringProperty(name="Eastings") northings: StringProperty(name="Northings") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 4bb1ff02d1..7537e5731f 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1,6 +1,5 @@ import os import bpy -from . import bcfstore from bpy.types import Panel from bpy.props import StringProperty @@ -1652,286 +1651,6 @@ class BIM_PT_ifccsv(Panel): row.operator("bim.import_ifccsv", icon="IMPORT") -class BIM_PT_bcf(Panel): - bl_label = "BIM Collaboration Format (BCF)" - bl_idname = "BIM_PT_bcf" - 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 - layout.use_property_decorate = False - - scene = context.scene - props = bpy.context.scene.BCFProperties - - row = layout.row(align=True) - row.operator("bim.new_bcf_project", text="New Project") - row.operator("bim.load_bcf_project", text="Load Project") - - if not props.is_loaded: - return - - row.operator("bim.save_bcf_project", text="Save Project") - - row = layout.row() - row.prop(props, "name") - - row = layout.row() - row.prop(props, "author") - - props = bpy.context.scene.BCFProperties - row = layout.row() - row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index") - col = row.column(align=True) - col.operator("bim.add_bcf_topic", icon="ADD", text="") - if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] - col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True) - - if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] - row = layout.row() - row.enabled = topic.is_editable - row.prop(topic, "description", text="") - - row = layout.row() - row.prop(topic, "viewpoints") - row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") - row.operator("bim.add_bcf_viewpoint", icon="ADD", text="") - row.operator("bim.remove_bcf_viewpoint", icon="X", text="") - - col = layout.column(align=True) - if topic.type: - col.prop(topic, "type", emboss=topic.is_editable) - if topic.status: - col.prop(topic, "status", emboss=topic.is_editable) - if topic.priority: - col.prop(topic, "priority", emboss=topic.is_editable) - if topic.stage: - col.prop(topic, "stage", emboss=topic.is_editable) - if topic.assigned_to: - col.prop(topic, "assigned_to", emboss=topic.is_editable) - if topic.due_date: - col.prop(topic, "due_date", emboss=topic.is_editable) - - col = layout.column(align=True) - if topic.modified_date: - col.prop(topic, "modified_date", emboss=False) - col.prop(topic, "modified_author", emboss=False) - else: - col.prop(topic, "creation_date", emboss=False) - col.prop(topic, "creation_author", emboss=False) - - -class BIM_PT_bcf_metadata(Panel): - bl_label = "BCF Metadata" - bl_idname = "BIM_PT_bcf_metadata" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - bl_parent_id = "BIM_PT_bcf" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - scene = context.scene - props = bpy.context.scene.BCFProperties - - if props.active_topic_index >= len(props.topics): - layout.label(text="No BCF project is loaded") - return - - topic = props.topics[props.active_topic_index] - bcfxml = bcfstore.BcfStore.get_bcfxml() - bcf_topic = bcfxml.topics[topic.name] - - layout.label(text="Header Files:") - - if bcf_topic.header: - for index, f in enumerate(bcf_topic.header.files): - box = self.layout.box() - row = box.row(align=True) - row.label(text=f.filename, icon="FILE_BLANK") - if f.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = f.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) - row.operator("bim.remove_bcf_file", icon="X", text="").index = index - - row = box.row() - row.label(text="Date") - row.label(text=f.date) - - if f.ifc_project: - row = box.row() - row.label(text="IFC Project") - row.label(text=f.ifc_project) - - if f.ifc_spatial_structure_element: - row = box.row() - row.label(text="IFC Spatial Structure Element") - row.label(text=f.ifc_spatial_structure_element) - - row = layout.row(align=True) - row.prop(topic, "file_reference") - row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="") - row = layout.row() - row.prop(topic, "file_ifc_project") - row = layout.row() - row.prop(topic, "file_ifc_spatial_structure_element") - - row = layout.row() - row.operator("bim.add_bcf_header_file") - - layout.label(text="Reference Links:") - for index, link in enumerate(topic.reference_links): - row = layout.row(align=True) - row.prop(link, "name") - row.operator("bim.open_uri", icon="URL", text="").uri = link.name - row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index - row = layout.row() - row.prop(topic, "reference_link") - row = layout.row() - row.operator("bim.add_bcf_reference_link") - - layout.label(text="Labels:") - for index, label in enumerate(topic.labels): - row = layout.row(align=True) - row.prop(label, "name", text="") - row.operator("bim.remove_bcf_label", icon="X", text="").index = index - row = layout.row() - row.prop(topic, "label") - row = layout.row() - row.operator("bim.add_bcf_label") - - layout.label(text="BIM Snippet:") - if topic.bim_snippet.schema: - row = layout.row(align=True) - row.prop(topic.bim_snippet, "type", emboss=False) - if topic.bim_snippet.schema: - row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema - - row = layout.row(align=True) - row.prop(topic.bim_snippet, "reference", emboss=False) - if topic.bim_snippet.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference) - row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") - else: - row = layout.row(align=True) - row.prop(topic, "bim_snippet_reference") - row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="") - row = layout.row() - row.prop(topic, "bim_snippet_type") - row = layout.row() - row.prop(topic, "bim_snippet_schema") - row = layout.row() - row.operator("bim.add_bcf_bim_snippet") - - layout.label(text="Document References:") - for index, doc in enumerate(topic.document_references): - box = self.layout.box() - row = box.row(align=True) - row.prop(doc, "reference") - if doc.is_external: - row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference - else: - op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") - op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference) - row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index - row = box.row(align=True) - row.prop(doc, "description") - row = layout.row(align=True) - row.prop(topic, "document_reference") - row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="") - row = layout.row() - row.prop(topic, "document_reference_description") - row = layout.row() - row.operator("bim.add_bcf_document_reference") - - layout.label(text="Related Topics:") - for index, related_topic in enumerate(topic.related_topics): - row = layout.row(align=True) - row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name - row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index - row = layout.row() - row.prop(topic, "related_topic") - row = layout.row() - row.operator("bim.add_bcf_related_topic") - - -class BIM_PT_bcf_comments(Panel): - bl_label = "BCF Comments" - bl_idname = "BIM_PT_bcf_comments" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - bl_parent_id = "BIM_PT_bcf" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - scene = context.scene - props = bpy.context.scene.BCFProperties - - if props.active_topic_index >= len(props.topics): - layout.label(text="No BCF project is loaded") - return - - row = layout.row() - row.prop(props, "comment_text_width") - - topic = props.topics[props.active_topic_index] - for comment in topic.comments: - box = self.layout.box() - - author_text = "{} ({})".format(comment.author, comment.date) - if comment.modified_author: - author_text = "*{} ({})".format(comment.modified_author, comment.modified_date) - row = box.row(align=True) - row.label(text=author_text, icon="WORDWRAP_ON") - row.prop( - comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True - ) - row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name - - if comment.is_editable: - row = box.row() - row.prop(comment, "comment", text="") - else: - col = box.column(align=True) - col.scale_y = 0.8 - words = comment.comment.split() - while words: - total_line_chars = 0 - line_words = [] - while words and total_line_chars < props.comment_text_width: - word = words.pop(0) - line_words.append(word) - total_line_chars += len(word) + 1 # 1 is for the space - col.label(text=" ".join(line_words)) - - row = layout.row() - row.prop(topic, "comment") - row = layout.row() - row.prop(topic, "has_related_viewpoint") - row = layout.row() - row.operator("bim.add_bcf_comment") - - class BIM_PT_qa(Panel): bl_label = "BIMTester Quality Auditing" bl_idname = "BIM_PT_qa" From c502d39aaf777c9a9b13dc8a403ad5d136c025a7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 29 Dec 2020 22:49:22 +1100 Subject: [PATCH 71/82] Add support for BCFXML export of IFC Clash results --- src/bcf/bcf/bcfxml.py | 2 +- .../blenderbim/bim/module/bcf/operator.py | 2 + .../blenderbim/bim/module/bcf/prop.py | 19 +++-- .../blenderbim/bim/operator.py | 8 +- src/ifcclash/ifcclash.py | 79 ++++++++++++++++--- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index deca2ccdeb..8a730b4417 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -345,7 +345,7 @@ class BcfXml: def write_viewpoint(self, viewpoint, topic): document = minidom.Document() - root = self._create_element(document, "VisualizationInfo", { "Guid": viewpoint.guid }) + root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid}) self.write_viewpoint_components(viewpoint, root) self.write_viewpoint_orthogonal_camera(viewpoint, root) self.write_viewpoint_perspective_camera(viewpoint, root) diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py index e953023915..f7784e0fa2 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py @@ -2,6 +2,8 @@ import os import bpy import bcf from . import bcfstore +from math import radians, degrees, atan, tan, cos, sin +from mathutils import Vector, Matrix, Euler, geometry class NewBcfProject(bpy.types.Operator): bl_idname = "bim.new_bcf_project" diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py index 8401a069f5..82426afc42 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py @@ -18,32 +18,37 @@ bcfviewpoints_enum = None def updateBcfReferenceLink(self, context): - bpy.ops.bim.edit_bcf_reference_links() + if bpy.context.scene.BCFProperties.is_loaded: + bpy.ops.bim.edit_bcf_reference_links() def updateBcfLabel(self, context): - bpy.ops.bim.edit_bcf_labels() + if bpy.context.scene.BCFProperties.is_loaded: + bpy.ops.bim.edit_bcf_labels() def updateBcfProjectName(self, context): - bpy.ops.bim.edit_bcf_project_name() + if bpy.context.scene.BCFProperties.is_loaded: + bpy.ops.bim.edit_bcf_project_name() def updateBcfAuthor(self, context): - bpy.ops.bim.edit_bcf_author() + if bpy.context.scene.BCFProperties.is_loaded: + bpy.ops.bim.edit_bcf_author() def updateBcfTopicName(self, context): - bpy.ops.bim.edit_bcf_topic_name() + if bpy.context.scene.BCFProperties.is_loaded: + bpy.ops.bim.edit_bcf_topic_name() def updateBcfTopicIsEditable(self, context): - if not self.is_editable: + if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable: bpy.ops.bim.edit_bcf_topic() def updateBcfCommentIsEditable(self, context): - if not self.is_editable: + if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable: bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 2073e496df..70ffd213af 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1568,11 +1568,12 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator): class ExecuteIfcClash(bpy.types.Operator): bl_idname = "bim.execute_ifc_clash" bl_label = "Execute IFC Clash" - filename_ext = ".json" + filename_ext = ".bcf" filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + if ".json" not in bpy.data.filepath: + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf") WindowManager = context.window_manager WindowManager.fileselect_add(self) return {"RUNNING_MODAL"} @@ -1581,7 +1582,8 @@ class ExecuteIfcClash(bpy.types.Operator): import ifcclash settings = ifcclash.IfcClashSettings() - self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + if ".json" not in self.filepath: + self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") settings.output = self.filepath settings.logger = logging.getLogger("Clash") settings.logger.setLevel(logging.DEBUG) diff --git a/src/ifcclash/ifcclash.py b/src/ifcclash/ifcclash.py index 449c881481..816675e163 100644 --- a/src/ifcclash/ifcclash.py +++ b/src/ifcclash/ifcclash.py @@ -11,6 +11,7 @@ import sys import argparse import logging + class Mesh: faces: [] vertices: [] @@ -153,6 +154,64 @@ class IfcClasher: return None def export(self): + if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf": + return self.export_bcfxml() + self.export_json() + + def export_bcfxml(self): + import bcf + import bcf.bcfxml + + for i, clash_set in enumerate(self.clash_sets): + bcfxml = bcf.bcfxml.BcfXml() + bcfxml.new_project() + bcfxml.project.name = clash_set["name"] + bcfxml.edit_project() + for key, clash in clash_set["clashes"].items(): + topic = bcf.data.Topic() + topic.title = "{}/{} and {}/{}".format( + clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"] + ) + topic = bcfxml.add_topic(topic) + viewpoint = bcf.data.Viewpoint() + viewpoint.perspective_camera = bcf.data.PerspectiveCamera() + position = np.array(clash["position"]) + point = position + np.array((5, 5, 5)) # Dumb, but works! + viewpoint.perspective_camera.camera_view_point.x = point[0] + viewpoint.perspective_camera.camera_view_point.y = point[1] + viewpoint.perspective_camera.camera_view_point.z = point[2] + mat = self.get_track_to_matrix(point, position) + viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1 + viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1 + viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1 + viewpoint.perspective_camera.camera_up_vector.x = mat[0][1] + viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] + viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] + bcfxml.add_viewpoint(topic, viewpoint) + if i == 0: + bcfxml.save_project(self.settings.output) + else: + bcfxml.save_project(self.settings.output + f".{i}") + + # https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706 + def get_track_to_matrix(self, camera_position, target_position): + camera_direction = camera_position - target_position + camera_direction = camera_direction / np.linalg.norm(camera_direction) + camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction) + camera_right = camera_right / np.linalg.norm(camera_right) + camera_up = np.cross(camera_direction, camera_right) + camera_up = camera_up / np.linalg.norm(camera_up) + rotation_transform = np.zeros((4, 4)) + rotation_transform[0, :3] = camera_right + rotation_transform[1, :3] = camera_up + rotation_transform[2, :3] = camera_direction + rotation_transform[-1, -1] = 1 + translation_transform = np.eye(4) + translation_transform[:3, -1] = - camera_position + look_at_transform = np.matmul(rotation_transform, translation_transform) + return np.linalg.inv(look_at_transform) + + def export_json(self): results = self.clash_sets.copy() for result in results: del result["a_cm"] @@ -269,14 +328,14 @@ class IfcClasher: def smart_group_clashes(self, clash_sets, max_clustering_distance): from sklearn.cluster import OPTICS from collections import defaultdict - + count_of_input_clashes = 0 count_of_clash_sets = 0 count_of_smart_groups = 0 count_of_final_clash_sets = 0 count_of_clash_sets = len(clash_sets) - + for clash_set in clash_sets: if not "clashes" in clash_set.keys(): print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.") @@ -324,14 +383,14 @@ class IfcClasher: if not "clashes" in clash_set.keys(): continue smart_groups = defaultdict(list) - for clash_id, content in clash_set['clashes'].items(): + for clash_id, content in clash_set["clashes"].items(): if "smart_group" in content: object_id_list = list() # Clash has been grouped, let's extract it. - object_id_list.append(content['a_global_id']) - object_id_list.append(content['b_global_id']) - smart_groups[content['smart_group']].append(object_id_list) - count_of_smart_groups += len(smart_groups) + object_id_list.append(content["a_global_id"]) + object_id_list.append(content["b_global_id"]) + smart_groups[content["smart_group"]].append(object_id_list) + count_of_smart_groups += len(smart_groups) output_clash_sets[clash_set["name"]].append(smart_groups) # Rename the clash groups to something more sensible @@ -346,8 +405,10 @@ class IfcClasher: i += 1 count_of_final_clash_sets = len(output_clash_sets) - print(f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned", - f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets") + print( + f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned", + f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets", + ) return output_clash_sets From 0d457ba0ce1abc6e5791b7a477a1a7c37114fd2c Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 29 Dec 2020 19:10:29 +0700 Subject: [PATCH 72/82] breakline decoration. close #1214 --- .../blenderbim/bim/decoration.py | 85 +++++++++++++++---- src/ifcblenderexport/blenderbim/bim/prop.py | 2 + 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index 6748501c33..f3fc9d62ae 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -37,6 +37,10 @@ class BaseDecorator(): #define CALLOUT_GAP 8.0 #define CALLOUT_SIZE 64.0 #define COLOR vec4({color[0]}, {color[1]}, {color[2]}, {color[3]}) + #define BREAK_LENGTH 32.0 + #define BREAK_WIDTH 16.0 + + #define UNPROJ(v, p) (unwindowMatrix * vec4(v, 0, 0) * p.w); """ VERT_GLSL = """ @@ -745,22 +749,6 @@ class SectionDecorator(LevelDecorator): } """ - FRAG_GLSL = """ - in vec2 gl_FragCoord; - in float dist; - out vec4 fragColor; - - void main() { - float t = fract(dist / DASH2_SIZE); - // pattern: 11011110 - if (t > 0.875 || (t > 0.25 && t < 0.375)) { - discard; - } else { - fragColor = COLOR; - } - } - """ - def draw_labels(self, obj, splines): region = self.context.region region3d = self.context.region_data @@ -774,3 +762,68 @@ class SectionDecorator(LevelDecorator): continue text = "RL " + self.format_value(verts[-1].z) self.draw_label(text, p0 + dir.normalized() * 16, -dir, gap=16, center=False) + + +class BreakDecorator(BaseDecorator): + """Decorator for dimension objects + - first edge of a mesh with zigzag thingy in the middle + + Uses first two vertices in verts list. + """ + basename = "IfcAnnotation/Break" + installed = None + + GEOM_GLSL = """ + uniform vec2 winsize; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + + void main() { + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + vec4 pm = (p1 - p0) * .5 + p0; + + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; + vec2 edge = p1w - p0w; + vec2 dir = normalize(edge); + vec2 quart = dir * BREAK_LENGTH * .25; + vec2 side = cross(vec3(dir, 0), vec3(0, 0, 1)).xy * BREAK_WIDTH; + + // TODO: check if there's enough length for zigzag + + gl_Position = p0; + EmitVertex(); + gl_Position = pm; + EmitVertex(); + gl_Position = pm + UNPROJ(quart - side, pm); + EmitVertex(); + gl_Position = pm + UNPROJ(quart * 3 + side, pm); + EmitVertex(); + gl_Position = pm + UNPROJ(quart * 4, pm); + EmitVertex(); + gl_Position = p1; + EmitVertex(); + EndPrimitive(); + } + """ + + def decorate(self, obj): + if obj.data.is_editmode: + verts = self.get_editmesh_geom(obj) + else: + verts = self.get_mesh_geom(obj) + self.draw_lines(obj, verts, [(0, 1)]) + + def get_mesh_geom(self, obj): + # first vertices only + vertices = [obj.matrix_world @ obj.data.vertices[i].co for i in (0, 1)] + return vertices + + def get_editmesh_geom(self, obj): + # first vertices only + mesh = bmesh.from_edit_mesh(obj.data) + vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts] + return vertices diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 2f5e19e6f2..270b9b630e 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -367,6 +367,7 @@ def toggleDecorations(self, context): decoration.PlanDecorator.install(self, context) decoration.SectionDecorator.install(self, context) decoration.MiscDecorator.install(self, context) + decoration.BreakDecorator.install(self, context) else: decoration.DimensionDecorator.uninstall() decoration.EqualityDecorator.uninstall() @@ -376,6 +377,7 @@ def toggleDecorations(self, context): decoration.PlanDecorator.uninstall() decoration.SectionDecorator.uninstall() decoration.MiscDecorator.uninstall() + decoration.BreakDecorator.uninstall() def getScenarios(self, context): From 7ae685dbffb7db3443090d8388a5a84849447590 Mon Sep 17 00:00:00 2001 From: aothms Date: Tue, 29 Dec 2020 07:59:52 -0500 Subject: [PATCH 73/82] Nix build: py39, boost1.71, no py34 on macos, boost url, no dae validator build --- nix/build-all.py | 17 +++++++---- .../pr622_and_disable_subdirs.patch | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 nix/patches/opencollada/pr622_and_disable_subdirs.patch diff --git a/nix/build-all.py b/nix/build-all.py index a943e25f94..536b7dba76 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -79,7 +79,7 @@ ch.setLevel(logging.INFO) logger.addHandler(ch) PROJECT_NAME="IfcOpenShell" -PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.2"] +PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.6", "3.9.1"] JSON_VERSION="v3.6.1" OCE_VERSION="0.18" # OCCT_VERSION="7.1.0" @@ -87,7 +87,7 @@ OCE_VERSION="0.18" # OCCT_VERSION="7.2.0" # OCCT_HASH="88af392" OCCT_VERSION="7.3.0p3" -BOOST_VERSION="1.59.0" +BOOST_VERSION="1.71.0" #PCRE_VERSION="8.39" PCRE_VERSION="8.41" #LIBXML2_VERSION="2.9.3" @@ -157,6 +157,13 @@ TOOLSET = None if get_os() == "Darwin": # C++11 features used in OCCT 7+ need a more recent stdlib TOOLSET = "10.9" if USE_OCCT else "10.6" + +# python 3.4 doesn't seem to build anymore on recent versions of clang +if get_os() == "Darwin": + try: + PYTHON_VERSIONS.remove("3.4.6") + except ValueError as e: + pass try: IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"] @@ -303,7 +310,7 @@ def run(cmds, cwd=None): BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_") OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,) -BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE) +BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,) # Helper functions @@ -580,7 +587,7 @@ if "OpenCOLLADA" in targets: download_url="https://github.com/KhronosGroup/OpenCOLLADA.git", download_name="OpenCOLLADA", download_tool=download_tool_git, - patch="./patches/opencollada/pr622.patch", + patch="./patches/opencollada/pr622_and_disable_subdirs.patch", revision=OPENCOLLADA_VERSION ) @@ -644,7 +651,7 @@ if "boost" in targets: list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \ list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \ ["stage", "-s", "NO_BZIP2=1"], - download_url="http://downloads.sourceforge.net/project/boost/boost/{BOOST_VERSION}/".format(**locals()), + download_url=BOOST_LOCATION, download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals()) ) diff --git a/nix/patches/opencollada/pr622_and_disable_subdirs.patch b/nix/patches/opencollada/pr622_and_disable_subdirs.patch new file mode 100644 index 0000000000..854b023af1 --- /dev/null +++ b/nix/patches/opencollada/pr622_and_disable_subdirs.patch @@ -0,0 +1,29 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 95abbe21..293c951c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -284,10 +284,10 @@ add_subdirectory(COLLADASaxFrameworkLoader) + add_subdirectory(COLLADAStreamWriter) + + # building COLLADAValidator app +-add_subdirectory(COLLADAValidator) ++# add_subdirectory(COLLADAValidator) + + # DAE validator app +-add_subdirectory(DAEValidator) ++# add_subdirectory(DAEValidator) + + # Library export + install(EXPORT LibraryExport DESTINATION ${OPENCOLLADA_INST_CMAKECONFIG} FILE OpenCOLLADATargets.cmake) +diff --git a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp +index 1f9a3eef..dd6f5c59 100644 +--- a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp ++++ b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp +@@ -10,6 +10,7 @@ + + #include "GeneratedSaxParserUtils.h" + #include ++#include + #include + #include + #include From 453dbd65ac14d6c02b343c22726b86485a6ee240 Mon Sep 17 00:00:00 2001 From: QwiglyDee Date: Tue, 29 Dec 2020 21:43:35 +0700 Subject: [PATCH 74/82] grid decorations. --- .../blenderbim/bim/decoration.py | 119 +++++++++++++++++- src/ifcblenderexport/blenderbim/bim/prop.py | 2 + 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index f3fc9d62ae..c17926a78c 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -21,9 +21,8 @@ class BaseDecorator(): DEF_GLSL = """ #define PI 3.141592653589793 + #define MAX_POINTS 64 #define CIRCLE_SEGS 24 - // max points = SEGS (circle) + 2 (stem) + 3 (arrow) - #define MAX_POINTS 29 #define ARROW1_ANGLE PI / 12.0 #define ARROW2_ANGLE PI / 3.0 #define ARROW1_SIZE 16.0 @@ -40,6 +39,10 @@ class BaseDecorator(): #define BREAK_LENGTH 32.0 #define BREAK_WIDTH 16.0 + #define DASH_SIZE 48.0 + #define CIRCLE_SIZE 16.0 + #define DASH_PATTERN 0x03C0FFFFU + #define UNPROJ(v, p) (unwindowMatrix * vec4(v, 0, 0) * p.w); """ @@ -261,7 +264,7 @@ class BaseDecorator(): self.shader.uniform_float("winsize", (region.width, region.height)) batch.draw(self.shader) - def draw_label(self, text, pos, dir, gap=4, center=True): + def draw_label(self, text, pos, dir, gap=4, center=True, vcenter=False): """Draw text label Args: @@ -275,11 +278,18 @@ class BaseDecorator(): blf.size(self.font_id, self.font_size, self.dpi) + w, h = 0, 0 + if center or vcenter: + w, h = blf.dimensions(self.font_id, text) + if center: - # centering - w, _ = blf.dimensions(self.font_id, text) + # horizontal centering pos -= Vector((cos, sin)) * w * 0.5 + if vcenter: + # vertical centering + pos -= Vector((-sin, cos)) * h * 0.5 + if gap: # side-shifting pos += Vector((-sin, cos)) * gap @@ -827,3 +837,102 @@ class BreakDecorator(BaseDecorator): mesh = bmesh.from_edit_mesh(obj.data) vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts] return vertices + + +class GridDecorator(BaseDecorator): + basename = "IfcGridAxis/" + installed = None + + GEOM_GLSL = """ + uniform vec2 winsize; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + + out float dist; // distance from starging point along segment + + void main() { + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + + mat4 windowMatrix = mat4(winsize.x/2, 0, 0, 0, 0, winsize.y/2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + mat4 unwindowMatrix = inverse(windowMatrix); + vec2 p0w = (windowMatrix * (p0 / p0.w)).xy, p1w = (windowMatrix * (p1 / p1.w)).xy; + vec2 edge = p1w - p0w; + vec2 dir = normalize(edge); + vec2 gap = dir * CIRCLE_SIZE; + + vec2 head[CIRCLE_SEGS]; + circle_head(p0w, p1w, CIRCLE_SIZE, head); + + dist = 0; + + for(int i=0; i Date: Wed, 30 Dec 2020 01:05:48 +0700 Subject: [PATCH 75/82] decoration code cleanedup/refactored --- .../blenderbim/bim/decoration.py | 444 +++++++++++------- src/ifcblenderexport/blenderbim/bim/prop.py | 24 +- 2 files changed, 278 insertions(+), 190 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index c17926a78c..ee5fc7239a 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -21,29 +21,40 @@ class BaseDecorator(): DEF_GLSL = """ #define PI 3.141592653589793 + #define COLOR vec4({color[0]}, {color[1]}, {color[2]}, {color[3]}) #define MAX_POINTS 64 #define CIRCLE_SEGS 24 - #define ARROW1_ANGLE PI / 12.0 - #define ARROW2_ANGLE PI / 3.0 - #define ARROW1_SIZE 16.0 - #define ARROW2_SIZE 24.0 - #define CIRCLE1_SIZE 8.0 - #define CIRCLE2_SIZE 8.0 - #define DASH1_SIZE 16.0 - #define DASH2_SIZE 48.0 - #define DASH_GAP 4.0 - #define CROSS_SIZE 16.0 - #define CALLOUT_GAP 8.0 - #define CALLOUT_SIZE 64.0 - #define COLOR vec4({color[0]}, {color[1]}, {color[2]}, {color[3]}) - #define BREAK_LENGTH 32.0 - #define BREAK_WIDTH 16.0 + """ - #define DASH_SIZE 48.0 - #define CIRCLE_SIZE 16.0 - #define DASH_PATTERN 0x03C0FFFFU + LIB_GLSL = """ + #define matCLIP2WIN() vec4(winsize.x/2, winsize.y/2, 1, 1) + #define matWIN2CLIP() vec4(2/winsize.x, 2/winsize.y, 1, 1) + #define CLIP2WIN(v) (clip2win * v / v.w) + #define WIN2CLIP(v) (win2clip * v * v.w) - #define UNPROJ(v, p) (unwindowMatrix * vec4(v, 0, 0) * p.w); + void arrow_head(in vec4 dir, in float size, in float angle, out vec4 head[3]) { + vec4 nose = dir * size; + float c = cos(angle), s = sin(angle); + head[0] = nose; + head[1] = vec4(mat2(c, -s, +s, c) * nose.xy, 0, 0); + head[2] = vec4(mat2(c, +s, -s, c) * nose.xy, 0, 0); + } + + void circle_head(in float size, out vec4 head[CIRCLE_SEGS]) { + float angle_d = PI * 2 / CIRCLE_SEGS; + for(int i = 0; i 0.5) { - discard; - } else { - fragColor = COLOR; - } + uint bit = uint(fract(dist / DASH_SIZE) * 32); + if ((DASH_PATTERN & (1U< Date: Wed, 30 Dec 2020 02:44:46 +0700 Subject: [PATCH 76/82] aligned grid labels horizontally --- src/ifcblenderexport/blenderbim/bim/decoration.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/decoration.py b/src/ifcblenderexport/blenderbim/bim/decoration.py index ee5fc7239a..41adc031f2 100644 --- a/src/ifcblenderexport/blenderbim/bim/decoration.py +++ b/src/ifcblenderexport/blenderbim/bim/decoration.py @@ -1020,9 +1020,7 @@ class GridDecorator(BaseDecorator): v1 = Vector(vertices[1]) p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) - dir = p1 - p0 - if dir.length < 1: - return + dir = Vector((1, 0)) text = obj.BIMObjectProperties.attributes['AxisTag'].string_value self.draw_label(text, p0, dir, vcenter=True, gap=0) self.draw_label(text, p1, dir, vcenter=True, gap=0) From cd6c2586872adf5cabe5ca116b66665d6aa952ab Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 30 Dec 2020 12:18:41 +1100 Subject: [PATCH 77/82] IFC Clash in the BlenderBIM Add-on now generates snapshots of clashes in BCF --- src/bcf/bcf/bcfxml.py | 2 +- .../blenderbim/bim/module/bcf/operator.py | 3 +++ .../blenderbim/bim/operator.py | 22 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 4 ++-- src/ifcclash/ifcclash.py | 13 +++++++++++ 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index 8a730b4417..bc4eaadee0 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -476,7 +476,7 @@ class BcfXml: if viewpoint.snapshot: topic_filepath = os.path.join(self.filepath, topic.guid) filepath = os.path.join(topic_filepath, viewpoint.snapshot) - if not os.path.exists(filepath): + if not os.path.exists(filepath) or topic_filepath not in filepath: filename = viewpoint.guid + "." + viewpoint.snapshot[-3:] copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename)) viewpoint.snapshot = filename diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py index f7784e0fa2..26af41b9f6 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py @@ -10,9 +10,11 @@ class NewBcfProject(bpy.types.Operator): bl_label = "New BCF Project" def execute(self, context): + bpy.context.scene.BCFProperties.is_loaded = False bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml.new_project() bpy.ops.bim.load_bcf_project() + bpy.context.scene.BCFProperties.is_loaded = True return {"FINISHED"} @@ -22,6 +24,7 @@ class LoadBcfProject(bpy.types.Operator): filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): + bpy.context.scene.BCFProperties.is_loaded = False bcfxml = bcfstore.BcfStore.get_bcfxml() if self.filepath: bcfxml.get_project(self.filepath) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index cda7f76740..102b525102 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1588,6 +1588,28 @@ class ExecuteIfcClash(bpy.types.Operator): settings.logger = logging.getLogger("Clash") settings.logger.setLevel(logging.DEBUG) ifc_clasher = ifcclash.IfcClasher(settings) + + if bpy.context.scene.BIMProperties.should_create_clash_snapshots: + def get_viewpoint_snapshot(self, viewpoint, mat): + camera = bpy.data.objects.get('IFC Clash Camera') + if not camera: + camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera")) + bpy.context.scene.collection.objects.link(camera) + camera.matrix_world = Matrix(mat) + bpy.context.scene.camera = camera + camera.data.angle = radians(60) + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" + area.spaces[0].shading.show_xray = True + bpy.context.scene.render.resolution_x = 480 + bpy.context.scene.render.resolution_y = 270 + bpy.context.scene.render.image_settings.file_format = "PNG" + bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png") + bpy.ops.render.opengl(write_still=True) + return bpy.context.scene.render.filepath + + ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot + ifc_clasher.clash_sets = [] for clash_set in bpy.context.scene.BIMProperties.clash_sets: self.a = [] diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 9fcf196b91..aceb19ce5b 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1358,6 +1358,7 @@ class BIMProperties(PropertyGroup): blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty) blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty) clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet) + should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True) clash_results_path: StringProperty(name="Clash Results Path") smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path") active_clash_set_index: IntProperty(name="Active Clash Set Index") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 7537e5731f..032542d7f6 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2172,9 +2172,9 @@ class BIM_PT_ifcclash(Panel): row.prop(source, "selector", text="") row = layout.row() + row.prop(props, "should_create_clash_snapshots") + row = layout.row(align=True) row.operator("bim.execute_ifc_clash") - - row = layout.row() row.operator("bim.select_ifc_clash_results") diff --git a/src/ifcclash/ifcclash.py b/src/ifcclash/ifcclash.py index 816675e163..a17cc79d00 100644 --- a/src/ifcclash/ifcclash.py +++ b/src/ifcclash/ifcclash.py @@ -187,12 +187,25 @@ class IfcClasher: viewpoint.perspective_camera.camera_up_vector.x = mat[0][1] viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] + viewpoint.components = bcf.data.Components() + c1 = bcf.data.Component() + c1.ifc_guid = clash["a_global_id"] + c2 = bcf.data.Component() + c2.ifc_guid = clash["b_global_id"] + viewpoint.components.selection.append(c1) + viewpoint.components.selection.append(c2) + viewpoint.components.visibility = bcf.data.ComponentVisibility() + viewpoint.components.visibility.default_visibility = True + viewpoint.snapshot = self.get_viewpoint_snapshot(viewpoint, mat) bcfxml.add_viewpoint(topic, viewpoint) if i == 0: bcfxml.save_project(self.settings.output) else: bcfxml.save_project(self.settings.output + f".{i}") + def get_viewpoint_snapshot(self, viewpoint, mat): + return None # Possible to overload this function in a GUI application if used as a library + # https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706 def get_track_to_matrix(self, camera_position, target_position): camera_direction = camera_position - target_position From ee222628f33ab1048d5183e4f66092ff67249e1a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 30 Dec 2020 12:20:02 +1100 Subject: [PATCH 78/82] Add Vim swp files to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b4ee87ef66..4f60342ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ __pycache__ .idea # gettext binary translation files *.mo +# Vim +*.swp From c844076efb75605c9bb69fde6809925e99b32cad Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 30 Dec 2020 13:50:10 +1100 Subject: [PATCH 79/82] New experimental option to export file from memory in preparation for incremental editing --- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 5 +++++ src/ifcblenderexport/blenderbim/bim/ifc.py | 3 ++- src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 0bbae0e5c2..935ce350e7 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1345,6 +1345,9 @@ class IfcExporter: self.roundtrip_id_new_to_old = {} def export(self, selected_objects): + self.file = ifc.IfcStore.get_file() + if self.file and self.ifc_export_settings.should_export_from_memory: + return self.write_ifc_file() self.schema_version = self.ifc_export_settings.schema self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version) self.file = ifcopenshell.file(schema=self.schema_version) @@ -3526,6 +3529,7 @@ class IfcExportSettings: ] self.should_use_presentation_style_assignment = False self.should_guess_quantities = False + self.should_export_from_memory = False self.context_tree = [] @staticmethod @@ -3545,6 +3549,7 @@ class IfcExportSettings: settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep settings.should_force_triangulation = scene_bim.export_should_force_triangulation settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native + settings.should_export_from_memory = scene_bim.export_should_export_from_memory settings.context_tree = [] for ifc_context in ["model", "plan"]: if getattr(scene_bim, "has_{}_context".format(ifc_context)): diff --git a/src/ifcblenderexport/blenderbim/bim/ifc.py b/src/ifcblenderexport/blenderbim/bim/ifc.py index 147a205c15..d2efa09c71 100644 --- a/src/ifcblenderexport/blenderbim/bim/ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/ifc.py @@ -12,5 +12,6 @@ class IfcStore: def get_file(): if IfcStore.file is None: IfcStore.path = bpy.context.scene.BIMProperties.ifc_file - IfcStore.file = ifcopenshell.open(IfcStore.path) + if IfcStore.path: + IfcStore.file = ifcopenshell.open(IfcStore.path) return IfcStore.file diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index aceb19ce5b..9c662d01b4 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1263,6 +1263,7 @@ class BIMProperties(PropertyGroup): ) 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_export_from_memory: BoolProperty(name="Export from Memory", 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_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 032542d7f6..5eb1473460 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1888,6 +1888,8 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, "import_export_should_roundtrip_native") row = layout.row() + row.prop(bim_properties, "export_should_export_from_memory") + row = layout.row() row.prop(bim_properties, "import_should_use_cpu_multiprocessing") row = layout.row() row.prop(bim_properties, "import_should_import_with_profiling") From 9def2613165fb1a3105d34784476f6a399f23e84 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 30 Dec 2020 21:09:19 +1100 Subject: [PATCH 80/82] Importing now stores a link to the original IFC representation contexts and subcontext IDs --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/import_ifc.py | 57 ++++--------------- src/ifcblenderexport/blenderbim/bim/prop.py | 8 ++- src/ifcblenderexport/blenderbim/bim/ui.py | 34 ++++++----- 4 files changed, 41 insertions(+), 59 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 2d7ab5bc4e..24cc944e8a 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -246,6 +246,7 @@ if bpy is not None: prop.DrawingStyle, prop.Sheet, prop.Subcontext, + prop.Representation, prop.PresentationLayer, prop.BIMProperties, prop.BIMDebugProperties, diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 961a189983..f59a7401b3 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -848,7 +848,7 @@ class IfcImporter: self.add_element_classifications(element, obj) self.add_element_document_relations(element, obj) self.add_type_product_psets(element, obj) - self.add_product_representation_contexts(element, obj) + self.add_product_representations(element, obj) self.type_collection.objects.link(obj) self.type_products[element.GlobalId] = obj @@ -974,7 +974,7 @@ class IfcImporter: self.add_defines_by_type_relation(element, obj) self.add_opening_relation(element, obj) self.add_product_definitions(element, obj) - self.add_product_representation_contexts(element, obj) + self.add_product_representations(element, obj) self.added_data[element.GlobalId] = obj if element.is_a("IfcOpeningElement"): @@ -1355,57 +1355,23 @@ class IfcImporter: bpy.ops.mesh.normals_make_consistent(context_override) bpy.ops.object.editmode_toggle(context_override) - def add_product_representation_contexts(self, element, obj): - subcontexts = [] - ifc_definition_ids = [] + def add_product_representations(self, element, obj): if element.is_a("IfcProduct"): if not element.Representation: return for r in element.Representation.Representations: - ifc_definition_ids.append(r.id()) - if r.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"): - subcontexts.append( - "{}/{}/{}".format( - r.ContextOfItems.ContextType or "", - r.ContextOfItems.ContextIdentifier or "", - r.ContextOfItems.TargetView or "", - ) - ) - else: - subcontexts.append( - "{}/{}/{}".format( - r.ContextOfItems.ContextType or "", r.ContextOfItems.ContextIdentifier or "", "" - ) - ) + 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: - ifc_definition_ids.append(r.id()) - if r.MappedRepresentation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"): - subcontexts.append( - "{}/{}/{}".format( - r.MappedRepresentation.ContextOfItems.ContextType or "", - r.MappedRepresentation.ContextOfItems.ContextIdentifier or "", - r.MappedRepresentation.ContextOfItems.TargetView or "", - ) - ) - else: - subcontexts.append( - "{}/{}/{}".format( - r.MappedRepresentation.ContextOfItems.ContextType or "", - r.MappedRepresentation.ContextOfItems.ContextIdentifier or "", - "", - ) - ) - for i, subcontext in enumerate(subcontexts): - representation_context = obj.BIMObjectProperties.representation_contexts.add() - ( - representation_context.context, - representation_context.name, - representation_context.target_view, - ) = subcontext.split("/") - representation_context.ifc_definition_id = ifc_definition_ids[i] + 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): if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy: @@ -1580,6 +1546,7 @@ class IfcImporter: 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": diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 9c662d01b4..b80e6a159d 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -565,6 +565,12 @@ class Subcontext(PropertyGroup): 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): name: StringProperty(name="Name") material: PointerProperty(name="Material", type=bpy.types.Material) @@ -1502,7 +1508,7 @@ class BIMObjectProperties(PropertyGroup): has_boundary_condition: BoolProperty(name="Has Boundary Condition") boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) - representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext) + representations: CollectionProperty(name="Representations", type=Representation) # Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress address: PointerProperty(name="Address", type=Address) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 5eb1473460..90ed6a32f4 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1,5 +1,6 @@ import os import bpy +from . import ifc from bpy.types import Panel from bpy.props import StringProperty @@ -637,27 +638,34 @@ class BIM_PT_representations(Panel): layout = self.layout props = context.active_object.BIMObjectProperties - if not props.representation_contexts: + 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="") - op = row.operator("bim.switch_context", icon="ADD", text="") - op.has_target_context = False + # TODO: reimplement with incremental editing + # op = row.operator("bim.switch_context", icon="ADD", text="") + # op.has_target_context = False - for index, subcontext in enumerate(props.representation_contexts): + self.file = ifc.IfcStore.get_file() + for index, representation in enumerate(props.representations): row = layout.row(align=True) - row.prop(subcontext, "context", text="") - row.prop(subcontext, "name", text="") - row.prop(subcontext, "target_view", text="") - op = row.operator("bim.switch_context", icon="OUTLINER_DATA_MESH", text="") - op.has_target_context = True - op.context_name = subcontext.context - op.subcontext_name = subcontext.name - op.target_view_name = subcontext.target_view - row.operator("bim.remove_context", icon="X", text="").index = index + 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): From de5ce28a78305955c46a76fbbe82a1c0c9c9a125 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Wed, 30 Dec 2020 14:25:08 +0100 Subject: [PATCH 81/82] --auto-section and --auto-elevation flags --- src/ifcconvert/IfcConvert.cpp | 10 ++ src/serializers/SvgSerializer.cpp | 176 ++++++++++++++++++++++++------ src/serializers/SvgSerializer.h | 20 +++- 3 files changed, 169 insertions(+), 37 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index c75ce4ffd0..45b45f2489 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -370,6 +370,10 @@ int main(int argc, char** argv) { "Element at which vertical cross sections should be created") ("elevation-ref", po::value(&elevation_ref), "Element at which vertical elevations should be created") + ("auto-section", + "Creates SVG cross section drawings automatically based on model extents") + ("auto-elevation", + "Creates SVG elevation drawings automatically based on model extents") ("door-arcs", "Draw door openings arcs for IfcDoor elements") ("section-height", po::value(§ion_height), "Specifies the cut section height for SVG 2D geometry.") @@ -946,6 +950,12 @@ int main(int argc, char** argv) { if (vmap.count("elevation-ref")) { static_cast(serializer.get())->setElevationRef(elevation_ref); } + if (vmap.count("auto-section")) { + static_cast(serializer.get())->setAutoSection(true); + } + if (vmap.count("auto-elevation")) { + static_cast(serializer.get())->setAutoElevation(true); + } if (relative_center_x && relative_center_y) { static_cast(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y); } diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 35c7259522..34162ba013 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -397,7 +397,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { double elev = p ? p->second : std::numeric_limits::quiet_NaN(); geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) }; - if (buffer_elements_) { + if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_) { element_buffer_.push_back(data); } @@ -533,6 +533,7 @@ void SvgSerializer::write(const geometry_data& data) { std::pair range; gp_Vec projection_direction; + gp_Pln projection_plane; IfcUtil::IfcBaseEntity* storey = nullptr; std::string drawing_name; @@ -555,6 +556,7 @@ void SvgSerializer::write(const geometry_data& data) { } else if (variant.which() == 2) { const auto& section = boost::get(variant); projection_direction = section.plane.Axis().Direction(); + projection_plane = section.plane; drawing_name = section.name; use_hlr = section.with_projection; } @@ -562,7 +564,40 @@ void SvgSerializer::write(const geometry_data& data) { auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored; if (use_hlr && hlr) { - hlr->Add(compound_to_use); + + // Check if any of the bounding box points is on the correct side of the plane + Bnd_Box bb; + try { + BRepBndLib::Add(compound_to_use, bb); + } + catch (const Standard_Failure&) {} + + if (bb.IsVoid()) { + continue; + } + + double xs[2], ys[2], zs[2]; + bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]); + + bool any = false; + + for (int i = 0; i < 8; ++i) { + gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]); + auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ()); + int state; + if (std::abs(d) < 1.e-5) { + state = 0; + } else { + state = d < 0. ? -1 : 1; + } + if (state == 1) { + any = true; + } + } + + if (any) { + hlr->Add(compound_to_use); + } } TopoDS_Iterator it(compound_to_use); @@ -865,13 +900,16 @@ void SvgSerializer::resize() { sc = (*scale_) * 1000; cx = (xmax + xmin) / 2. * sc - width * center_x_.get_value_or(0.5); cy = (ymax + ymin) / 2. * sc - height * center_y_.get_value_or(0.5); - } else { + } + else { if (calculated_scale_) { sc = *calculated_scale_; - } else { + } + else { if (dx / width > dy / height) { sc = width / dx; - } else { + } + else { sc = height / dy; } calculated_scale_ = sc; @@ -893,8 +931,12 @@ void SvgSerializer::resize() { (*it)->value() *= sc; } } +} +void SvgSerializer::resetScale() { // reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale. + // this is a separate call now as we first need to read drawing extents for automatically positioning sections and + // elevations xmin = +std::numeric_limits::infinity(); ymin = +std::numeric_limits::infinity(); xmax = -std::numeric_limits::infinity(); @@ -904,6 +946,63 @@ void SvgSerializer::resize() { void SvgSerializer::finalize() { resize(); + if (!deferred_section_data_.is_initialized() && (auto_section_ || auto_elevation_)) { + deferred_section_data_.emplace(); + } + + // @nb keep in mind Y-axis is negated in these 6 definitions to account + // for coordinate system differences. + if (auto_section_) { + { + gp_Pln pln(gp_Ax3( + gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / 2., 0.), + gp_Dir(-1, 0, 0), + gp_Dir(0, -1, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Section North South", false }); + } + { + gp_Pln pln(gp_Ax3( + gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / -2., 0.), + gp_Dir(0, -1, 0), + gp_Dir(-1, 0, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Section East West", true }); + } + } + + if (auto_elevation_) { + { + gp_Pln pln(gp_Ax3( + gp_Pnt((xmin + xmax) / 2., -(ymin - 1.e-1), 0.), + gp_Dir(0, -1, 0), + gp_Dir(-1, 0, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true }); + } + { + gp_Pln pln(gp_Ax3( + gp_Pnt(xmax + 1.e-1, (ymin + ymax) / -2., 0.), + gp_Dir(-1, 0, 0), + gp_Dir(0, -1, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true }); + } + { + gp_Pln pln(gp_Ax3( + gp_Pnt((xmin + xmax) / 2., -(ymax + 1.e-1), 0.), + gp_Dir(0, -1, 0), + gp_Dir(1, 0, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true }); + } + { + gp_Pln pln(gp_Ax3( + gp_Pnt(xmin - 1.e-1, (ymin + ymax) / -2., 0.), + gp_Dir(-1, 0, 0), + gp_Dir(0, -1, 0))); + deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true }); + } + } + + resetScale(); + + if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) { // Draw door arcs only on floor plans. @@ -922,7 +1021,7 @@ void SvgSerializer::finalize() { hlr = new HLRBRep_Algo; } - *section_data_ = { sd }; + section_data_ = std::vector{ sd }; for (auto& e : element_buffer_) { write(e); } @@ -939,35 +1038,40 @@ void SvgSerializer::finalize() { HLRBRep_HLRToShape hlr_shapes(hlr); auto hlr_compound_unmirrored = hlr_shapes.VCompound(); - // Compound 3D curves for mirroring to work - ShapeFix_Edge sfe; - TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); + if (!hlr_compound_unmirrored.IsNull()) { + + // Compound 3D curves for mirroring to work + ShapeFix_Edge sfe; + TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); + } + + // Mirror to match SVG coord system. + // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and + // not on the TopoDS_Shape input. + + gp_Trsf trsf_mirror; + trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); + make_transform_mirror.Build(); + auto hlr_compound = make_transform_mirror.Shape(); + + exp.Init(hlr_compound, TopAbs_EDGE); + BRep_Builder B; + auto& po = start_path(drawing_name, "class=\"projection\""); + for (; exp.More(); exp.Next()) { + TopoDS_Wire w; + B.MakeWire(w); + B.Add(w, exp.Current()); + write(po, w); + } + } - - // Mirror to match SVG coord system. - // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and - // not on the TopoDS_Shape input. - - gp_Trsf trsf_mirror; - trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); - BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); - make_transform_mirror.Build(); - auto hlr_compound = make_transform_mirror.Shape(); - - exp.Init(hlr_compound, TopAbs_EDGE); - BRep_Builder B; - auto& po = start_path(drawing_name, "class=\"projection\""); - for (; exp.More(); exp.Next()) { - TopoDS_Wire w; - B.MakeWire(w); - B.Add(w, exp.Current()); - write(po, w); - } } resize(); + resetScale(); if (use_hlr) { hlr.Nullify(); @@ -987,7 +1091,9 @@ void SvgSerializer::finalize() { if (it->first.first) { svg_file << " first.first) << ">\n"; } else { - svg_file << " first.second << "\" class=\"section\">\n"; + auto n = it->first.second; + IfcUtil::escape_xml(n); + svg_file << " \n"; } } svg_file << " second.first << ">\n"; @@ -1071,10 +1177,13 @@ return oss.str(); } std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element* elem) { + auto n = elem->name(); + IfcUtil::escape_xml(n); + return nameElement_({ {"id", with_section_heights_from_storey_ ? object_id(storey, elem) : GeometrySerializer::object_id(elem)}, {"class", elem->type()}, - {"data-name", elem->name()}, + {"data-name", n}, {"data-guid", elem->guid()} }); } @@ -1099,6 +1208,7 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) { std::string ifc_name; if (!elem->get("Name")->isNull()) { ifc_name = (std::string) *elem->get("Name"); + IfcUtil::escape_xml(ifc_name); } return nameElement_({ diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index b69e1feb71..178e336e42 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -119,7 +119,8 @@ protected: boost::optional scale_, calculated_scale_, center_x_, center_y_; bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_; - bool draw_door_arcs_, buffer_elements_, is_floor_plan_; + bool draw_door_arcs_, is_floor_plan_; + bool auto_section_, auto_elevation_; IfcParse::IfcFile* file; IfcUtil::IfcBaseEntity* storey_; @@ -146,8 +147,9 @@ public: , print_space_names_(false) , print_space_areas_(false) , draw_door_arcs_(false) - , buffer_elements_(false) , is_floor_plan_(true) + , auto_section_(false) + , auto_elevation_(false) , file(0) , storey_(0) , xcoords_begin(0) @@ -176,15 +178,25 @@ public: void setPrintSpaceNames(bool b) { print_space_names_ = b; } void setPrintSpaceAreas(bool b) { print_space_areas_ = b; } void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; } + void resize(); + void resetScale(); + void setSectionRef(const boost::optional& s) { section_ref_ = s; - buffer_elements_ = true; } void setElevationRef(const boost::optional& s) { elevation_ref_ = s; - buffer_elements_ = true; } + + void setAutoSection(bool b) { + auto_section_ = b; + } + + void setAutoElevation(bool b) { + auto_elevation_ = b; + } + void setScale(double s) { scale_ = s; } void setDrawingCenter(double x, double y) { center_x_ = x; center_y_ = y; From a5adb01dff8363ba1428d32ac03aaff4e471a048 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Wed, 30 Dec 2020 14:44:43 +0100 Subject: [PATCH 82/82] IfcConvert --svg-xmlns option --- src/ifcconvert/IfcConvert.cpp | 3 +++ src/serializers/SvgSerializer.cpp | 13 ++++++++----- src/serializers/SvgSerializer.h | 10 ++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 45b45f2489..63926249d1 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -374,6 +374,8 @@ int main(int argc, char** argv) { "Creates SVG cross section drawings automatically based on model extents") ("auto-elevation", "Creates SVG elevation drawings automatically based on model extents") + ("svg-xmlns", + "Stores name and guid in a separate namespace as opposed to data-name, data-guid") ("door-arcs", "Draw door openings arcs for IfcDoor elements") ("section-height", po::value(§ion_height), "Specifies the cut section height for SVG 2D geometry.") @@ -956,6 +958,7 @@ int main(int argc, char** argv) { if (vmap.count("auto-elevation")) { static_cast(serializer.get())->setAutoElevation(true); } + static_cast(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0); if (relative_center_x && relative_center_y) { static_cast(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y); } diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 34162ba013..1d2381436d 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -1093,7 +1093,7 @@ void SvgSerializer::finalize() { } else { auto n = it->first.second; IfcUtil::escape_xml(n); - svg_file << " \n"; + svg_file << " \n"; } } svg_file << " second.first << ">\n"; @@ -1113,6 +1113,9 @@ void SvgSerializer::finalize() { void SvgSerializer::writeHeader() { svg_file << "type()}, - {"data-name", n}, - {"data-guid", elem->guid()} + {namespace_prefix_ + "name", n}, + {namespace_prefix_ + "guid", elem->guid()} }); } @@ -1214,8 +1217,8 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) { return nameElement_({ {"id", idElement(elem)}, {"class", entity}, - {"data-name", ifc_name}, - {"data-guid", *elem->get("GlobalId")} + {namespace_prefix_ + "name", ifc_name}, + {namespace_prefix_ + "guid", *elem->get("GlobalId")} }); } diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 178e336e42..f483a69a47 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -121,6 +121,7 @@ protected: bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_; bool draw_door_arcs_, is_floor_plan_; bool auto_section_, auto_elevation_; + bool use_namespace_; IfcParse::IfcFile* file; IfcUtil::IfcBaseEntity* storey_; @@ -134,6 +135,8 @@ protected: std::list element_buffer_; Handle(HLRBRep_Algo) hlr; + + std::string namespace_prefix_; public: SvgSerializer(const std::string& out_filename, const SerializerSettings& settings) : GeometrySerializer(settings) @@ -150,11 +153,13 @@ public: , is_floor_plan_(true) , auto_section_(false) , auto_elevation_(false) + , use_namespace_(false) , file(0) , storey_(0) , xcoords_begin(0) , ycoords_begin(0) , radii_begin(0) + , namespace_prefix_("data-") {} void addXCoordinate(const boost::shared_ptr& fi) { xcoords.push_back(fi); } void addYCoordinate(const boost::shared_ptr& fi) { ycoords.push_back(fi); } @@ -197,6 +202,11 @@ public: auto_elevation_ = b; } + void setUseNamespace(bool b) { + use_namespace_ = b; + namespace_prefix_ = use_namespace_ ? "ifc:" : "data-"; + } + void setScale(double s) { scale_ = s; } void setDrawingCenter(double x, double y) { center_x_ = x; center_y_ = y;