Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0

This commit is contained in:
Carlos Villagrasa
2022-07-22 18:42:25 +02:00
committed by GitHub
36 changed files with 475 additions and 71 deletions
+9
View File
@@ -132,6 +132,15 @@ def get_enum_items(data, prop_name, context):
return items return items
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_panel(event):
x, y = event.mouse_x, event.mouse_y
bpy.context.window.cursor_warp(10, 10)
move_back = lambda: bpy.context.window.cursor_warp(x, y)
bpy.app.timers.register(move_back, first_interval=0.01)
class IfcHeaderExtractor: class IfcHeaderExtractor:
def __init__(self, filepath: str): def __init__(self, filepath: str):
self.filepath = filepath self.filepath = filepath
@@ -31,6 +31,7 @@ classes = (
operator.EnableEditingGroup, operator.EnableEditingGroup,
operator.DisableEditingGroup, operator.DisableEditingGroup,
operator.SelectGroupProducts, operator.SelectGroupProducts,
operator.UpdateGroup,
prop.Group, prop.Group,
prop.BIMGroupProperties, prop.BIMGroupProperties,
ui.BIM_PT_groups, ui.BIM_PT_groups,
@@ -22,6 +22,7 @@ import ifcopenshell.api
import blenderbim.bim.helper import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data from ifcopenshell.api.group.data import Data
from ifcopenshell.util.selector import Selector
class LoadGroups(bpy.types.Operator): class LoadGroups(bpy.types.Operator):
@@ -36,6 +37,7 @@ class LoadGroups(bpy.types.Operator):
new = props.groups.add() new = props.groups.add()
new.ifc_definition_id = ifc_definition_id new.ifc_definition_id = ifc_definition_id
new.name = group["Name"] new.name = group["Name"]
new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else ""
props.is_editing = True props.is_editing = True
bpy.ops.bim.disable_editing_group() bpy.ops.bim.disable_editing_group()
return {"FINISHED"} return {"FINISHED"}
@@ -167,7 +169,7 @@ class AssignGroup(bpy.types.Operator):
"group.assign_group", "group.assign_group",
self.file, self.file,
**{ **{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), "product": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
"group": self.file.by_id(self.group), "group": self.file.by_id(self.group),
} }
) )
@@ -219,3 +221,31 @@ class SelectGroupProducts(bpy.types.Operator):
if self.group in product_groups: if self.group in product_groups:
obj.select_set(True) obj.select_set(True)
return {"FINISHED"} return {"FINISHED"}
class UpdateGroup(bpy.types.Operator):
bl_idname = "bim.update_group"
bl_label = "Update Group"
bl_options = {"REGISTER", "UNDO"}
query: bpy.props.StringProperty()
group_id: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
group = self.file.by_id(self.group_id)
query = self.query
new_products = Selector.parse(self.file, query)
ifcopenshell.api.run(
"group.update_group_products",
self.file,
**{
"products": new_products,
"group": group,
}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups()
return {"FINISHED"}
@@ -34,7 +34,8 @@ from bpy.props import (
class Group(PropertyGroup): class Group(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
selection_query: StringProperty(name="Selection Query")
class BIMGroupProperties(PropertyGroup): class BIMGroupProperties(PropertyGroup):
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute) group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
@@ -40,7 +40,7 @@ class BIM_PT_groups(Panel):
self.props = context.scene.BIMGroupProperties self.props = context.scene.BIMGroupProperties
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER") row.label(text=f"{len(Data.groups)} Groups Found", icon="OUTLINER")
if self.props.is_editing: if self.props.is_editing:
row.operator("bim.add_group", text="", icon="ADD") row.operator("bim.add_group", text="", icon="ADD")
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL") row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
@@ -118,7 +118,7 @@ class BIM_UL_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.name) row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name)
group_id = item.ifc_definition_id group_id = item.ifc_definition_id
if context.scene.BIMGroupProperties.active_group_id == group_id: if context.scene.BIMGroupProperties.active_group_id == group_id:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
@@ -130,6 +130,10 @@ class BIM_UL_groups(UIList):
op.group = group_id op.group = group_id
op = row.operator("bim.remove_group", text="", icon="X") op = row.operator("bim.remove_group", text="", icon="X")
op.group = group_id op.group = group_id
if item.selection_query != "":
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
op.group_id = item.ifc_definition_id
op.query = item.selection_query
else: else:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
op.group = group_id op.group = group_id
@@ -137,12 +141,18 @@ class BIM_UL_groups(UIList):
op.group = group_id op.group = group_id
op = row.operator("bim.remove_group", text="", icon="X") op = row.operator("bim.remove_group", text="", icon="X")
op.group = group_id op.group = group_id
if item.selection_query != "":
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
op.group_id = item.ifc_definition_id
op.query = item.selection_query
class BIM_UL_object_groups(UIList): class BIM_UL_object_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.name) row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name)
op = row.operator("bim.remove_group", text="", icon="X")
op.group = item.ifc_definition_id
op = row.operator("bim.assign_group", text="", icon="ADD") op = row.operator("bim.assign_group", text="", icon="ADD")
op.group = item.ifc_definition_id op.group = item.ifc_definition_id
@@ -315,12 +315,16 @@ class AppendLibraryElement(bpy.types.Operator):
bl_idname = "bim.append_library_element" bl_idname = "bim.append_library_element"
bl_label = "Append Library Element" bl_label = "Append Library Element"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Append element to the current project"
definition: bpy.props.IntProperty() definition: bpy.props.IntProperty()
prop_index: bpy.props.IntProperty() prop_index: bpy.props.IntProperty()
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return IfcStore.get_file() poll = bool(IfcStore.get_file())
if bpy.app.version > (3, 0, 0) and not poll:
cls.poll_message_set("Please create or load a project first.")
return poll
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
@@ -38,6 +38,7 @@ classes = (
operator.SaveSelectorQuery, operator.SaveSelectorQuery,
operator.OpenQueryLibrary, operator.OpenQueryLibrary,
operator.LoadQuery, operator.LoadQuery,
operator.AddToIfcGroup,
prop.BIMFilterClasses, prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys, prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties, prop.BIMSearchProperties,
@@ -20,9 +20,11 @@ import re
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
from ifcopenshell.api.group.data import Data
from ifcopenshell.util.selector import Selector from ifcopenshell.util.selector import Selector
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import close_operator_panel
from itertools import cycle from itertools import cycle
from bpy.types import PropertyGroup, Operator from bpy.types import PropertyGroup, Operator
from bpy.props import ( from bpy.props import (
@@ -462,7 +464,6 @@ class ActivateIfcBuildingStoreyFilter(Operator):
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
class UnhideAllElements(Operator): class UnhideAllElements(Operator):
"""Filter model elements based on selection""" """Filter model elements based on selection"""
@@ -479,7 +480,6 @@ class UnhideAllElements(Operator):
class FilterModelElements(Operator): class FilterModelElements(Operator):
"""Filter model elements based on selection""" """Filter model elements based on selection"""
bl_idname = "bim.filter_model_elements" bl_idname = "bim.filter_model_elements"
bl_label = "Filter Model Elements" bl_label = "Filter Model Elements"
option: StringProperty("select|isolate|hide") option: StringProperty("select|isolate|hide")
@@ -513,7 +513,7 @@ class FilterModelElements(Operator):
selection = self.add_filters(selection, query) selection = self.add_filters(selection, query)
elif query.selector == "GlobalId": elif query.selector == "GlobalId":
selection += f"#{query.global_id}" selection += f"#{query.value}"
elif query.selector == "IfcElementType": elif query.selector == "IfcElementType":
index = int(query.active_sub_option.split(":")[0]) index = int(query.active_sub_option.split(":")[0])
@@ -562,7 +562,6 @@ class FilterModelElements(Operator):
class IfcSelector(Operator): class IfcSelector(Operator):
"""Select elements in model with IFC Selector""" """Select elements in model with IFC Selector"""
bl_idname = "bim.ifc_selector" bl_idname = "bim.ifc_selector"
bl_label = "Select elements with IFC Selector" bl_label = "Select elements with IFC Selector"
@@ -603,19 +602,11 @@ class SaveSelectorQuery(Operator):
class OpenQueryLibrary(Operator): class OpenQueryLibrary(Operator):
"""Open Query Library""" """Open Query Library"""
bl_idname = "bim.open_query_library" bl_idname = "bim.open_query_library"
bl_label = "Open Query Library" bl_label = "Open Query Library"
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400) return context.window_manager.invoke_popup(self, width=400)
def close_panel(event):
x, y = event.mouse_x, event.mouse_y
bpy.context.window.cursor_warp(10, 10)
move_back = lambda: bpy.context.window.cursor_warp(x, y)
bpy.app.timers.register(move_back, first_interval=0.001)
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
@@ -641,8 +632,37 @@ class LoadQuery(Operator):
bl_idname = "bim.load_query" bl_idname = "bim.load_query"
bl_label = "Load Query" bl_label = "Load Query"
index: IntProperty() index: IntProperty()
def invoke(self, context, event):
close_operator_panel(event)
return self.execute(context)
def execute(self, context): def execute(self, context):
ifc_selector = context.scene.IfcSelectorProperties ifc_selector = context.scene.IfcSelectorProperties
ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query
return {"FINISHED"} return {"FINISHED"}
class AddToIfcGroup(Operator):
bl_idname = "bim.add_to_ifc_group"
bl_label = "Add to IFC Group"
group_name: StringProperty(name="Group Name")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
layout.prop(self, "group_name")
def execute(self, context):
self.file = IfcStore.get_file()
ifc_selector = context.scene.IfcSelectorProperties
selector_query_syntax = ifc_selector.selector_query_syntax
group = ifcopenshell.api.run("group.add_group", self.file, **{"Name": self.group_name, "Description": f'*selector*{selector_query_syntax}*selector*'})
objects = Selector.parse(self.file, selector_query_syntax)
ifcopenshell.api.run("group.assign_group", self.file, **{"product": objects, "group": group})
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -153,7 +153,8 @@ class IfcSelectorUI:
row = layout.row(align=True) row = layout.row(align=True)
row.alignment = "CENTER" row.alignment = "CENTER"
row.operator("bim.save_selector_query", text="Save Query") row.operator("bim.save_selector_query", text="Save Query")
row.operator("bim.open_query_library", text="Load Query") op = row.operator("bim.open_query_library", text="Load Query")
row.operator("bim.add_to_ifc_group", text="Add to IFC Group")
def draw_query_group_ui(self, ifc_selector, layout): def draw_query_group_ui(self, ifc_selector, layout):
for index, group in enumerate(ifc_selector.groups): for index, group in enumerate(ifc_selector.groups):
@@ -662,7 +662,7 @@ class AddStructuralLoadGroup(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file) load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
ifcopenshell.api.run("group.assign_group", self.file, product=load_group, group=self.file.by_id(self.load_case)) ifcopenshell.api.run("group.assign_group", self.file, product=[load_group], group=self.file.by_id(self.load_case))
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
return {"FINISHED"} return {"FINISHED"}
@@ -754,7 +754,7 @@ class AddStructuralActivity(bpy.types.Operator):
structural_member=element, structural_member=element,
) )
ifcopenshell.api.run( ifcopenshell.api.run(
"group.assign_group", self.file, product=activity, group=self.file.by_id(self.load_group) "group.assign_group", self.file, product=[activity], group=self.file.by_id(self.load_group)
) )
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group) bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
+8 -2
View File
@@ -27,7 +27,7 @@ from . import schema
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty from blenderbim.bim.prop import StrProperty
from blenderbim.bim.ui import IFCFileSelector from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim.helper import get_enum_items from blenderbim.bim.helper import get_enum_items, close_operator_panel
from mathutils import Vector, Matrix, Euler from mathutils import Vector, Matrix, Euler
from math import radians from math import radians
@@ -531,6 +531,7 @@ def update_enum_property_search_prop(self, context):
for i, prop in enumerate(self.collection_names): for i, prop in enumerate(self.collection_names):
if prop.name == self.dummy_name: if prop.name == self.dummy_name:
setattr(context.data, self.prop_name, self.collection_identifiers[i].name) setattr(context.data, self.prop_name, self.collection_identifiers[i].name)
close_operator_panel(self)
break break
@@ -542,8 +543,11 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
collection_names: bpy.props.CollectionProperty(type=StrProperty) collection_names: bpy.props.CollectionProperty(type=StrProperty)
collection_identifiers: bpy.props.CollectionProperty(type=StrProperty) collection_identifiers: bpy.props.CollectionProperty(type=StrProperty)
prop_name: bpy.props.StringProperty() prop_name: bpy.props.StringProperty()
mouse_x: bpy.props.IntProperty()
mouse_y: bpy.props.IntProperty()
def invoke(self, context, event): def invoke(self, context, event):
self.mouse_x, self.mouse_y = event.mouse_x, event.mouse_y
self.clear_collections() self.clear_collections()
self.data = context.data self.data = context.data
items = get_enum_items(self.data, self.prop_name, context) items = get_enum_items(self.data, self.prop_name, context)
@@ -551,7 +555,9 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
self.add_items_regular(items) self.add_items_regular(items)
self.add_items_suggestions() self.add_items_suggestions()
return context.window_manager.invoke_props_dialog(self) # Cursor is moved when we update dummy_name, set it back
context.window.cursor_warp(self.mouse_x, self.mouse_y)
return context.window_manager.invoke_props_popup(self, event)
def draw(self, context): def draw(self, context):
# Mandatory to access context.data in update : # Mandatory to access context.data in update :
+3 -3
View File
@@ -148,7 +148,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
) )
group = ifc.run("group.add_group") group = ifc.run("group.add_group")
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"}) ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
ifc.run("group.assign_group", group=group, product=element) ifc.run("group.assign_group", group=group, product=[element])
collector.assign(camera) collector.assign(camera)
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing") pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
ifc.run( ifc.run(
@@ -203,7 +203,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
context=context, context=context,
ifc_representation_class=drawing_tool.get_ifc_representation_class(object_type), ifc_representation_class=drawing_tool.get_ifc_representation_class(object_type),
) )
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=element) ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=[element])
collector.assign(obj) collector.assign(obj)
drawing_tool.enable_editing(obj) drawing_tool.enable_editing(obj)
@@ -251,7 +251,7 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context) annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context)
if annotation: if annotation:
ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation) ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation)
ifc.run("group.assign_group", group=group, product=annotation) ifc.run("group.assign_group", group=group, product=[annotation])
collector.assign(ifc.get_object(annotation)) collector.assign(ifc.get_object(annotation))
if reference_obj and ifc.is_moved(reference_obj): if reference_obj and ifc.is_moved(reference_obj):
+14 -3
View File
@@ -6,7 +6,8 @@ Running tests
The BlenderBIM Add-on has three layers of tests for each of its three technology The BlenderBIM Add-on has three layers of tests for each of its three technology
layers. These roughly form a test pyramid, moving from many abstract domain layers. These roughly form a test pyramid, moving from many abstract domain
logic tests, to low-level concrete unit tests, to a minimal number of UI and logic tests, to low-level concrete unit tests, to a minimal number of UI and
smoke tests. These tests use ``pytest`` as the test framework and runner. smoke tests. These tests use ``pytest`` as the test framework and runner, so you
will need to install ``pytest``.
All development is expected to use test driven development, and so we expect All development is expected to use test driven development, and so we expect
test coverage to be 100% where it is technically possible to test. test coverage to be 100% where it is technically possible to test.
@@ -39,8 +40,8 @@ similar.
Tool tests Tool tests
---------- ----------
The tool layer tests actual concrete functions. These have the following The tool layer tests actual concrete functions. You will need to install the
dependencies: following dependencies:
* pytest-blender, accessible to your system's Python * pytest-blender, accessible to your system's Python
* Blender executable, accessible to pytest-blender on your system's Python * Blender executable, accessible to pytest-blender on your system's Python
@@ -51,6 +52,16 @@ dependencies:
You can install the dependencies by running the ``scripts/setup_pytest.py`` You can install the dependencies by running the ``scripts/setup_pytest.py``
script in Blender. script in Blender.
.. warning::
The ``scripts/setup_pytest.py`` may not work for all operating systems and
installation environments. In this case, you may be required to install the
dependencies manually.
Please be aware that some Blender may come packaged with its own Python,
which may be separate to the Python installation on your system. Be sure to
install the dependencies to the correct Python environment.
Then, run the tests. This will launch Blender headlessly and check the behaviour Then, run the tests. This will launch Blender headlessly and check the behaviour
of all concrete functions. of all concrete functions.
+32 -8
View File
@@ -16,16 +16,40 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import subprocess
import sys import sys
from pathlib import Path import subprocess
print("Here are the detected system paths:")
print(sys.path)
py_exec = str(sys.executable) py_exec = str(sys.executable)
lib = Path(py_exec).parent.parent / "lib"
print("Detected executable:", py_exec)
subprocess.call([py_exec, "-m", "ensurepip", "--user"]) subprocess.call([py_exec, "-m", "ensurepip", "--user"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"]) subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pytest"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pytest-blender"]) sys_paths = [p for p in sys.path if "site-packages" in p]
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pytest-bdd"]) if sys_paths:
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pygments"]) print("Detected installation directory:", sys_paths[-1])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest-bdd"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pytest-blender"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "pygments"])
else:
print("Could not detect installation directory. Good luck.")
subprocess.call([py_exec, "-m", "pip", "install", "pytest"])
subprocess.call([py_exec, "-m", "pip", "install", "pytest-bdd"])
subprocess.call([py_exec, "-m", "pip", "install", "pytest-blender"])
subprocess.call([py_exec, "-m", "pip", "install", "pygments"])
try:
import pytest
import pytest_bdd
import pytest_blender
import pygments
print("Test dependency installation was successful!")
except Exception as e:
print("Installation failed :(")
print(e)
@@ -0,0 +1,17 @@
@search
Feature: Search
Scenario: Select all walls
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a new group to IfcSelector
And I add a new query to IfcSelector
And I set "scene.IfcSelectorProperties.groups[0].queries[0].selector" to "IFC Class"
And I set "scene.IfcSelectorProperties.groups[0].queries[0].active_option" to "124: IfcWall"
When I press "bim.filter_model_elements(option='select')"
Then the object "IfcWall/Cube" is selected
+9
View File
@@ -99,6 +99,15 @@ def i_add_a_sun():
def i_add_a_material(): def i_add_a_material():
bpy.context.active_object.active_material = bpy.data.materials.new("Material") bpy.context.active_object.active_material = bpy.data.materials.new("Material")
@given("I add a new group to IfcSelector")
@when("I add a new group to IfcSelector")
def i_add_a_new_collection_item():
bpy.data.scenes["Scene"].IfcSelectorProperties.groups.add()
@given("I add a new query to IfcSelector")
@when("I add a new query to IfcSelector")
def i_add_a_new_collection_item():
bpy.data.scenes["Scene"].IfcSelectorProperties.groups[0].queries.add()
@given(parsers.parse('the material "{name}" colour is set to "{colour}"')) @given(parsers.parse('the material "{name}" colour is set to "{colour}"'))
@when(parsers.parse('the material "{name}" colour is set to "{colour}"')) @when(parsers.parse('the material "{name}" colour is set to "{colour}"'))
+2 -2
View File
@@ -223,7 +223,7 @@ class TestAddDrawing:
ifc.run( ifc.run(
"group.edit_group", group="group", attributes={"Name": "name", "ObjectType": "DRAWING"} "group.edit_group", group="group", attributes={"Name": "name", "ObjectType": "DRAWING"}
).should_be_called() ).should_be_called()
ifc.run("group.assign_group", group="group", product="element").should_be_called() ifc.run("group.assign_group", group="group", product=["element"]).should_be_called()
collector.assign("obj").should_be_called() collector.assign("obj").should_be_called()
ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset") ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset")
ifc.run( ifc.run(
@@ -293,7 +293,7 @@ class TestAddAnnotation:
ifc_representation_class="ifc_representation_class", ifc_representation_class="ifc_representation_class",
).should_be_called().will_return("element") ).should_be_called().will_return("element")
drawing.get_drawing_group("drawing").should_be_called().will_return("group") drawing.get_drawing_group("drawing").should_be_called().will_return("group")
ifc.run("group.assign_group", group="group", product="element").should_be_called() ifc.run("group.assign_group", group="group", product=["element"]).should_be_called()
collector.assign("obj").should_be_called() collector.assign("obj").should_be_called()
drawing.enable_editing("obj").should_be_called() drawing.enable_editing("obj").should_be_called()
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type") subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
+2 -2
View File
@@ -222,7 +222,7 @@ class TestAssign(NewFile):
tool.Ifc.link(element, element_obj) tool.Ifc.link(element, element_obj)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get()) group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
group.ObjectType = "DRAWING" group.ObjectType = "DRAWING"
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=element, group=group) ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=[element], group=group)
subject.assign(element_obj) subject.assign(element_obj)
assert element_obj.users_collection[0].name == "IfcGroup/Unnamed" assert element_obj.users_collection[0].name == "IfcGroup/Unnamed"
assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed") assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed")
@@ -235,7 +235,7 @@ class TestAssign(NewFile):
tool.Ifc.link(element, element_obj) tool.Ifc.link(element, element_obj)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get()) group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
group.ObjectType = "DRAWING" group.ObjectType = "DRAWING"
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=element, group=group) ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=[element], group=group)
subject.assign(element_obj) subject.assign(element_obj)
assert element_obj.users_collection[0].name == "IfcGroup/Unnamed" assert element_obj.users_collection[0].name == "IfcGroup/Unnamed"
assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed") assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed")
+2 -2
View File
@@ -260,7 +260,7 @@ class TestGetDrawingGroup(NewFile):
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation() element = ifc.createIfcAnnotation()
group = ifcopenshell.api.run("group.add_group", ifc) group = ifcopenshell.api.run("group.add_group", ifc)
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
assert subject.get_drawing_group(element) == group assert subject.get_drawing_group(element) == group
@@ -280,7 +280,7 @@ class TestGetGroupElements(NewFile):
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation() element = ifc.createIfcAnnotation()
group = ifcopenshell.api.run("group.add_group", ifc) group = ifcopenshell.api.run("group.add_group", ifc)
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
assert subject.get_group_elements(group) == (element,) assert subject.get_group_elements(group) == (element,)
@@ -23,7 +23,10 @@ import ifcopenshell.api
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {} self.settings = {
"Name": "Unnamed",
"Description": "",
}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
@@ -33,6 +36,7 @@ class Usecase:
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": "Unnamed", "Name": self.settings["Name"],
"Description": self.settings["Description"],
} }
) )
@@ -37,12 +37,13 @@ class Usecase:
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["product"]], "RelatedObjects": self.settings["product"],
"RelatingGroup": self.settings["group"], "RelatingGroup": self.settings["group"],
} }
) )
rel = self.settings["group"].IsGroupedBy[0] rel = self.settings["group"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set() related_objects = set(rel.RelatedObjects) or set()
related_objects.add(self.settings["product"]) for obj in self.settings["product"]:
related_objects.add(obj)
rel.RelatedObjects = list(related_objects) rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
@@ -20,7 +20,8 @@
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {"group": None, "attributes": {}} self.settings = {
"group": None, "attributes": {}}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
@@ -0,0 +1,33 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"group": None,
"products": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
rel = self.settings["group"].IsGroupedBy[0]
rel.RelatedObjects = self.settings["products"]
@@ -41,6 +41,8 @@ class Usecase:
properties = self.settings["pset"].HasProperties or [] properties = self.settings["pset"].HasProperties or []
elif self.settings["pset"].is_a("IfcQuantitySet"): elif self.settings["pset"].is_a("IfcQuantitySet"):
properties = self.settings["pset"].Quantities or [] properties = self.settings["pset"].Quantities or []
elif self.settings["pset"].is_a() in ("IfcMaterialProperties", "IfcProfileProperties"):
properties = self.settings["pset"].Properties or []
for prop in properties: for prop in properties:
self.file.remove(prop) self.file.remove(prop)
self.file.remove(self.settings["pset"]) self.file.remove(self.settings["pset"])
@@ -189,7 +189,7 @@ class Selector:
value = None value = None
for element in elements: for element in elements:
element_value = cls.get_element_value(element, key) element_value = cls.get_element_value(element, key)
if element_value is None and value is not None: if element_value is None and value is not None and "not" not in comparison:
continue continue
if comparison and cls.filter_element(element, element_value, comparison, value): if comparison and cls.filter_element(element, element_value, comparison, value):
results.append(element) results.append(element)
@@ -29,6 +29,22 @@ class TestRemovePset(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcRelDefinesByProperties")) == 0 assert len(self.file.by_type("IfcRelDefinesByProperties")) == 0
assert len(self.file.by_type("IfcPropertySet")) == 0 assert len(self.file.by_type("IfcPropertySet")) == 0
def test_removing_material_psets(self):
element = self.file.createIfcMaterial()
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
assert len(element.HasProperties) == 1
ifcopenshell.api.run("pset.remove_pset", self.file, product=element, pset=pset)
assert len(element.HasProperties) == 0
assert len(self.file.by_type("IfcMaterialProperties")) == 0
def test_removing_profile_psets(self):
element = self.file.createIfcProfileDef()
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
assert len(element.HasProperties) == 1
ifcopenshell.api.run("pset.remove_pset", self.file, product=element, pset=pset)
assert len(element.HasProperties) == 0
assert len(self.file.by_type("IfcMaterialProperties")) == 0
def test_only_unassigning_if_pset_is_used_by_other_elements(self): def test_only_unassigning_if_pset_is_used_by_other_elements(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+2 -2
View File
@@ -1111,7 +1111,7 @@ class TestIdsAuthoring(unittest.TestCase):
group = ifcopenshell.api.run("group.add_group", ifc) group = ifcopenshell.api.run("group.add_group", ifc)
facet = ids.partOf.create(entity="IfcGroup") facet = ids.partOf.create(entity="IfcGroup")
run("", facet=facet, inst=element, expected=False) run("", facet=facet, inst=element, expected=False)
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
run("", facet=facet, inst=element, expected=True) run("", facet=facet, inst=element, expected=True)
# An IfcGroup can be passed by subtypes # An IfcGroup can be passed by subtypes
@@ -1119,7 +1119,7 @@ class TestIdsAuthoring(unittest.TestCase):
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
group = ifc.createIfcInventory() group = ifc.createIfcInventory()
facet = ids.partOf.create(entity="IfcGroup") facet = ids.partOf.create(entity="IfcGroup")
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
run("", facet=facet, inst=element, expected=True) run("", facet=facet, inst=element, expected=True)
# An IfcSystem only checks that a system is assigned without any other logic # An IfcSystem only checks that a system is assigned without any other logic
@@ -112,6 +112,27 @@ class TestSelector(test.bootstrap.IFC4):
assert subject.Selector.parse(self.file, '.IfcElement[Name*="oba"]') == [element] assert subject.Selector.parse(self.file, '.IfcElement[Name*="oba"]') == [element]
assert subject.Selector.parse(self.file, '.IfcElement[Name*="abc"]') == [] assert subject.Selector.parse(self.file, '.IfcElement[Name*="abc"]') == []
def test_selecting_if_value_not_matching(self):
element_1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element_2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset_1 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_1, name="Foo_Bar")
pset_2 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_2, name="Foo_Bar")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_1, properties={"Foo": "Bar"})
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_2, properties={"Foo": "BOO"})
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "Bar"]') == [element_2]
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "BOO"]') == [element_1]
def test_selecting_when_attribute_is_none(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.Selector.parse(self.file, '.IfcElement[PredefinedType !="non-existent predefined type"]') == [element]
def test_selecting_a_property_which_includes_non_standard_characters(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="a !%$§&/()?|*-+,€~#@µ^°a")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a !%$§&/()?|*-+,€~#@µ^°a": "Bar"})
assert subject.Selector.parse(self.file, '.IfcElement[a !%$§&/()?|*-+,€~#@µ^°a.a !%$§&/()?|*-+,€~#@µ^°a="Bar"]') == [element]
def test_comparing_if_value_is_in_a_list(self): def test_comparing_if_value_is_in_a_list(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element.Name = "Foobar" element.Name = "Foobar"
+1
View File
@@ -42,6 +42,7 @@ def cast_to_value(from_value, to_value):
class Facet: class Facet:
def __init__(self, *parameters): def __init__(self, *parameters):
self.status = None
self.failed_entities = [] self.failed_entities = []
self.failed_reasons = [] self.failed_reasons = []
for i, name in enumerate(self.parameters): for i, name in enumerate(self.parameters):
+4 -1
View File
@@ -217,7 +217,8 @@ class Specification:
self.applicable_entities.append(element) self.applicable_entities.append(element)
for facet in self.requirements: for facet in self.requirements:
result = facet(element) result = facet(element)
if not bool(result): facet.status = bool(result)
if not facet.status:
self.failed_entities.add(element) self.failed_entities.add(element)
facet.failed_entities.append(element) facet.failed_entities.append(element)
facet.failed_reasons.append(str(result)) facet.failed_reasons.append(str(result))
@@ -227,5 +228,7 @@ class Specification:
self.status = False self.status = False
elif self.minOccurs != 0 and not self.applicable_entities: elif self.minOccurs != 0 and not self.applicable_entities:
self.status = False self.status = False
for facet in self.requirements:
facet.status = False
elif len(self.applicable_entities) > (self.maxOccurs or 1): elif len(self.applicable_entities) > (self.maxOccurs or 1):
self.status = False self.status = False
+3 -3
View File
@@ -28,10 +28,10 @@ class Reporter:
def report(self, ids): def report(self, ids):
pass pass
def to_string(): def to_string(self):
return "" return ""
def write(filepath): def write(self, filepath):
pass pass
@@ -142,7 +142,7 @@ class Json(Reporter):
requirements.append( requirements.append(
{ {
"description": requirement.to_string("requirement"), "description": requirement.to_string("requirement"),
"success": not requirement.failed_entities, "status": requirement.status,
"failed_entities": [ "failed_entities": [
{"reason": requirement.failed_reasons[i], "element": str(e)} {"reason": requirement.failed_reasons[i], "element": str(e)}
for i, e in enumerate(requirement.failed_entities[0:10]) for i, e in enumerate(requirement.failed_entities[0:10])
+2 -2
View File
@@ -1157,7 +1157,7 @@ class TestPartOf:
group = ifcopenshell.api.run("group.add_group", ifc) group = ifcopenshell.api.run("group.add_group", ifc)
facet = PartOf(entity="IfcGroup") facet = PartOf(entity="IfcGroup")
run("", facet=facet, inst=element, expected=False) run("", facet=facet, inst=element, expected=False)
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
run("", facet=facet, inst=element, expected=True) run("", facet=facet, inst=element, expected=True)
# An IfcGroup can be passed by subtypes # An IfcGroup can be passed by subtypes
@@ -1165,7 +1165,7 @@ class TestPartOf:
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
group = ifc.createIfcInventory() group = ifc.createIfcInventory()
facet = PartOf(entity="IfcGroup") facet = PartOf(entity="IfcGroup")
ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
run("", facet=facet, inst=element, expected=True) run("", facet=facet, inst=element, expected=True)
# An IfcSystem only checks that a system is assigned without any other logic # An IfcSystem only checks that a system is assigned without any other logic
+77
View File
@@ -0,0 +1,77 @@
# IfcTester - IDS based model auditing
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcTester.
#
# IfcTester is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcTester is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
import os
import time
import ifctester
import ifctester.reporter
import ifcopenshell
from flask import Flask, request, send_from_directory
app = Flask(__name__)
class Ifc:
ifc = None
filepath = None
@classmethod
def get(cls, filepath=None):
if filepath is None or filepath == cls.filepath:
return cls.ifc
cls.filepath = filepath
cls.ifc = ifcopenshell.open(filepath)
return cls.ifc
@app.route("/")
def index():
with open("www/index.html") as template:
return template.read()
@app.route("/<path:asset>.<string:ext>")
def get_asset(asset, ext):
if ext in ("js", "css"):
return send_from_directory("www", asset + "." + ext)
@app.route("/audit", methods=["POST"])
def audit():
filename = ifcopenshell.guid.new()
ids_filepath = os.path.join("uploads", filename + ".ids")
ifc_filepath = os.path.join("uploads", filename + ".ifc")
request.files.get("ids").save(ids_filepath)
request.files.get("ifc").save(ifc_filepath)
start = time.time()
specs = ifctester.open(ids_filepath)
ifc = Ifc.get(ifc_filepath)
print("Finished loading:", time.time() - start)
start = time.time()
specs.validate(ifc)
print("Finished validating:", time.time() - start)
start = time.time()
os.remove(ids_filepath)
os.remove(ifc_filepath)
engine = ifctester.reporter.Json(specs)
engine.report()
return engine.to_string()
+85 -3
View File
@@ -10,6 +10,7 @@ class IDSContainer extends HTMLElement {
this.filename = 'specifications.ids'; this.filename = 'specifications.ids';
this.ids = null; this.ids = null;
this.containerId = crypto.randomUUID(); this.containerId = crypto.randomUUID();
this.isEditing = true;
} }
} }
@@ -315,6 +316,13 @@ class IDSFacets extends HTMLElement {
} }
feather.replace(); feather.replace();
} }
showResults(requirements) {
var facetElements = this.getElementsByTagName('ids-facet');
for (var i=0; i<facetElements.length; i++) {
facetElements[i].showResults(requirements[i]);
}
}
} }
class IDSFacetInstructions extends HTMLElement { class IDSFacetInstructions extends HTMLElement {
@@ -401,6 +409,21 @@ class IDSFacet extends HTMLElement {
} }
} }
showResults(requirement) {
var idsResultElements = this.parentElement.getElementsByTagName('ids-result');
for (var i=0; i<idsResultElements.length; i++) {
if (! idsResultElements[i].classList.contains('hidden')) {
idsResultElements[i].classList.add('hidden');
}
if (requirement.status == true && idsResultElements[i].attributes['name'].value == 'pass') {
idsResultElements[i].classList.remove('hidden');
} else if (requirement.status == false && idsResultElements[i].attributes['name'].value == 'fail') {
idsResultElements[i].classList.remove('hidden');
idsResultElements[i].getElementsByTagName('span')[0].textContent = requirement.failed_entities.length;
}
}
}
renderTemplate(templates, parameters) { renderTemplate(templates, parameters) {
for (var i=0; i<templates.length; i++) { for (var i=0; i<templates.length; i++) {
var hasKeys = true; var hasKeys = true;
@@ -747,6 +770,13 @@ class IDSSpecs extends HTMLElement {
} }
feather.replace(); feather.replace();
} }
showResults(specifications) {
var specElements = this.getElementsByTagName('ids-spec');
for (var i=0; i<specElements.length; i++) {
specElements[i].showResults(specifications[i]);
}
}
} }
class IDSSpec extends HTMLElement { class IDSSpec extends HTMLElement {
@@ -786,6 +816,15 @@ class IDSSpec extends HTMLElement {
} }
} }
showResults(specification) {
var facetsElements = this.getElementsByTagName('ids-facets');
for (var i=0; i<facetsElements.length; i++) {
if (facetsElements[i].attributes['name'].value == "requirements") {
facetsElements[i].showResults(specification.requirements);
}
}
}
dragover(e) { dragover(e) {
// The HTML draggable API is terrible. // The HTML draggable API is terrible.
e.preventDefault(); e.preventDefault();
@@ -809,7 +848,7 @@ class IDSLoader extends HTMLElement {
inputElement.accept = '.ids,.xml'; inputElement.accept = '.ids,.xml';
inputElement.multiple = false; inputElement.multiple = false;
inputElement.addEventListener("change", this.loadFile) inputElement.addEventListener("change", this.loadFile)
inputElement.dispatchEvent(new MouseEvent("click")); inputElement.dispatchEvent(new MouseEvent("click"));
} }
loadFile(e) { loadFile(e) {
@@ -845,9 +884,8 @@ class IDSSave extends HTMLElement {
} }
click() { click() {
var xmlString = new XMLSerializer().serializeToString(this.closest('ids-container').ids);
console.log(xmlString);
var container = this.closest('ids-container') var container = this.closest('ids-container')
var xmlString = new XMLSerializer().serializeToString(container.ids);
this.download(container.filename, xmlString); this.download(container.filename, xmlString);
} }
@@ -862,9 +900,53 @@ class IDSSave extends HTMLElement {
} }
} }
class IDSAudit extends HTMLElement {
connectedCallback() {
this.addEventListener('click', this.launchFileBrowser);
}
launchFileBrowser(accept, callback) {
var inputElement = document.createElement("input");
inputElement.idsAudit = this;
inputElement.type = "file";
inputElement.accept = '.ifc';
inputElement.multiple = false;
inputElement.addEventListener("change", this.loadFile)
inputElement.dispatchEvent(new MouseEvent("click"));
}
loadFile(e) {
var self = this.idsAudit;
var container = self.closest('ids-container');
var request = new XMLHttpRequest();
request.onreadystatechange = function() { self.processResponse(request); };
request.open("POST", "audit");
var data = new FormData();
data.append('ifc', this.files[0]);
data.append('ids', new Blob([new XMLSerializer().serializeToString(container.ids)], {type:'text/plain'}));
request.send(data);
}
processResponse(request) {
if (request.readyState != 4) {
return;
}
var container = this.closest('ids-container');
container.isEditing = false;
var results = JSON.parse(request.responseText);
var specsElements = container.getElementsByTagName('ids-specs');
for (var i=0; i<specsElements.length; i++) {
var specs = specsElements[i];
specs.showResults(results.specifications);
}
}
}
window.customElements.define('ids-container', IDSContainer); window.customElements.define('ids-container', IDSContainer);
window.customElements.define('ids-loader', IDSLoader); window.customElements.define('ids-loader', IDSLoader);
window.customElements.define('ids-save', IDSSave); window.customElements.define('ids-save', IDSSave);
window.customElements.define('ids-audit', IDSAudit);
window.customElements.define('ids-info', IDSInfo); window.customElements.define('ids-info', IDSInfo);
window.customElements.define('ids-info-element', IDSInfoElement); window.customElements.define('ids-info-element', IDSInfoElement);
window.customElements.define('ids-specs', IDSSpecs); window.customElements.define('ids-specs', IDSSpecs);
+13 -4
View File
@@ -47,9 +47,11 @@
<i data-feather="download"></i> SAVE <i data-feather="download"></i> SAVE
</a> </a>
</ids-save> </ids-save>
<a href="#"> <ids-audit>
<i data-feather="play"></i> AUDIT MODEL <a href="#">
</a> <i data-feather="play"></i> AUDIT MODEL
</a>
</ids-audit>
<span> <span>
<a href="#"> <a href="#">
<i data-feather="x"></i> CLOSE <i data-feather="x"></i> CLOSE
@@ -139,6 +141,13 @@
<i data-feather="x"></i> <i data-feather="x"></i>
</ids-facet-remove> </ids-facet-remove>
</span> </span>
<ids-result name="fail" class="result-icon hidden">
<i data-feather="x"></i>
<span>32</span>
</ids-result>
<ids-result name="pass" class="result-icon hidden">
<i data-feather="check"></i>
</ids-result>
<ids-facet type="requirement"></ids-facet> <ids-facet type="requirement"></ids-facet>
<ids-facet-instructions class="requirement-instructions" title="Instructions"> <ids-facet-instructions class="requirement-instructions" title="Instructions">
Optionally write instructions about how to achieve this requirement. Optionally write instructions about how to achieve this requirement.
@@ -163,7 +172,7 @@
</ids-spec-move> </ids-spec-move>
</span> </span>
</div> </div>
<ids-spec> </ids-spec>
</template> </template>
</ids-specs> </ids-specs>
</div> </div>
+15 -5
View File
@@ -270,20 +270,30 @@ ids-spec ids-spec-handle .snippet {
bottom: -5px; bottom: -5px;
position: relative; position: relative;
} }
.result-icon { ids-result {
display: inline-block; display: inline-block;
position: absolute; position: absolute;
margin-left: -30px; margin-left: -30px;
margin-top: -2px; margin-top: -3px;
cursor: help; cursor: help;
} }
.result-icon.pass { ids-result span {
position: absolute;
float: right;
right: 30px;
background-color: #333;
color: white;
font-family: 'Nova Mono';
padding: 3px;
border-radius: 5px;
}
ids-result[name="pass"] {
color: var(--green); color: var(--green);
} }
.result-icon.fail { ids-result[name="fail"] {
color: var(--red); color: var(--red);
} }
.result-icon:hover { ids-result:hover {
color: var(--blue); color: var(--blue);
} }
.hidden { .hidden {