mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 10:57:49 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
@@ -132,6 +132,15 @@ def get_enum_items(data, prop_name, context):
|
||||
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:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
@@ -31,6 +31,7 @@ classes = (
|
||||
operator.EnableEditingGroup,
|
||||
operator.DisableEditingGroup,
|
||||
operator.SelectGroupProducts,
|
||||
operator.UpdateGroup,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
ui.BIM_PT_groups,
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data
|
||||
from ifcopenshell.util.selector import Selector
|
||||
|
||||
|
||||
class LoadGroups(bpy.types.Operator):
|
||||
@@ -36,6 +37,7 @@ class LoadGroups(bpy.types.Operator):
|
||||
new = props.groups.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = group["Name"]
|
||||
new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else ""
|
||||
props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_group()
|
||||
return {"FINISHED"}
|
||||
@@ -167,7 +169,7 @@ class AssignGroup(bpy.types.Operator):
|
||||
"group.assign_group",
|
||||
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),
|
||||
}
|
||||
)
|
||||
@@ -219,3 +221,31 @@ class SelectGroupProducts(bpy.types.Operator):
|
||||
if self.group in product_groups:
|
||||
obj.select_set(True)
|
||||
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):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
selection_query: StringProperty(name="Selection Query")
|
||||
|
||||
|
||||
class BIMGroupProperties(PropertyGroup):
|
||||
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
|
||||
|
||||
@@ -40,7 +40,7 @@ class BIM_PT_groups(Panel):
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
|
||||
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:
|
||||
row.operator("bim.add_group", text="", icon="ADD")
|
||||
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):
|
||||
if item:
|
||||
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
|
||||
if context.scene.BIMGroupProperties.active_group_id == group_id:
|
||||
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 = row.operator("bim.remove_group", text="", icon="X")
|
||||
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:
|
||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.group = group_id
|
||||
@@ -137,12 +141,18 @@ class BIM_UL_groups(UIList):
|
||||
op.group = group_id
|
||||
op = row.operator("bim.remove_group", text="", icon="X")
|
||||
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):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
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.group = item.ifc_definition_id
|
||||
|
||||
@@ -315,12 +315,16 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
bl_idname = "bim.append_library_element"
|
||||
bl_label = "Append Library Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Append element to the current project"
|
||||
definition: bpy.props.IntProperty()
|
||||
prop_index: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
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):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
@@ -38,6 +38,7 @@ classes = (
|
||||
operator.SaveSelectorQuery,
|
||||
operator.OpenQueryLibrary,
|
||||
operator.LoadQuery,
|
||||
operator.AddToIfcGroup,
|
||||
prop.BIMFilterClasses,
|
||||
prop.BIMFilterBuildingStoreys,
|
||||
prop.BIMSearchProperties,
|
||||
|
||||
@@ -20,9 +20,11 @@ import re
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.api.group.data import Data
|
||||
from ifcopenshell.util.selector import Selector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import close_operator_panel
|
||||
from itertools import cycle
|
||||
from bpy.types import PropertyGroup, Operator
|
||||
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="Deselect All").action = "DESELECT"
|
||||
|
||||
|
||||
class UnhideAllElements(Operator):
|
||||
"""Filter model elements based on selection"""
|
||||
|
||||
@@ -479,7 +480,6 @@ class UnhideAllElements(Operator):
|
||||
|
||||
class FilterModelElements(Operator):
|
||||
"""Filter model elements based on selection"""
|
||||
|
||||
bl_idname = "bim.filter_model_elements"
|
||||
bl_label = "Filter Model Elements"
|
||||
option: StringProperty("select|isolate|hide")
|
||||
@@ -513,7 +513,7 @@ class FilterModelElements(Operator):
|
||||
selection = self.add_filters(selection, query)
|
||||
|
||||
elif query.selector == "GlobalId":
|
||||
selection += f"#{query.global_id}"
|
||||
selection += f"#{query.value}"
|
||||
|
||||
elif query.selector == "IfcElementType":
|
||||
index = int(query.active_sub_option.split(":")[0])
|
||||
@@ -562,7 +562,6 @@ class FilterModelElements(Operator):
|
||||
|
||||
class IfcSelector(Operator):
|
||||
"""Select elements in model with IFC Selector"""
|
||||
|
||||
bl_idname = "bim.ifc_selector"
|
||||
bl_label = "Select elements with IFC Selector"
|
||||
|
||||
@@ -603,19 +602,11 @@ class SaveSelectorQuery(Operator):
|
||||
|
||||
class OpenQueryLibrary(Operator):
|
||||
"""Open Query Library"""
|
||||
|
||||
bl_idname = "bim.open_query_library"
|
||||
bl_label = "Open Query Library"
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(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)
|
||||
return context.window_manager.invoke_popup(self, width=400)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -641,8 +632,37 @@ class LoadQuery(Operator):
|
||||
bl_idname = "bim.load_query"
|
||||
bl_label = "Load Query"
|
||||
index: IntProperty()
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
close_operator_panel(event)
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
ifc_selector = context.scene.IfcSelectorProperties
|
||||
ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query
|
||||
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.alignment = "CENTER"
|
||||
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):
|
||||
for index, group in enumerate(ifc_selector.groups):
|
||||
|
||||
@@ -662,7 +662,7 @@ class AddStructuralLoadGroup(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_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())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -754,7 +754,7 @@ class AddStructuralActivity(bpy.types.Operator):
|
||||
structural_member=element,
|
||||
)
|
||||
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())
|
||||
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
|
||||
|
||||
@@ -27,7 +27,7 @@ from . import schema
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
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 math import radians
|
||||
|
||||
@@ -531,6 +531,7 @@ def update_enum_property_search_prop(self, context):
|
||||
for i, prop in enumerate(self.collection_names):
|
||||
if prop.name == self.dummy_name:
|
||||
setattr(context.data, self.prop_name, self.collection_identifiers[i].name)
|
||||
close_operator_panel(self)
|
||||
break
|
||||
|
||||
|
||||
@@ -542,8 +543,11 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
|
||||
collection_names: bpy.props.CollectionProperty(type=StrProperty)
|
||||
collection_identifiers: bpy.props.CollectionProperty(type=StrProperty)
|
||||
prop_name: bpy.props.StringProperty()
|
||||
mouse_x: bpy.props.IntProperty()
|
||||
mouse_y: bpy.props.IntProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mouse_x, self.mouse_y = event.mouse_x, event.mouse_y
|
||||
self.clear_collections()
|
||||
self.data = context.data
|
||||
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"}
|
||||
self.add_items_regular(items)
|
||||
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):
|
||||
# Mandatory to access context.data in update :
|
||||
|
||||
@@ -148,7 +148,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
)
|
||||
group = ifc.run("group.add_group")
|
||||
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)
|
||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||
ifc.run(
|
||||
@@ -203,7 +203,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
||||
context=context,
|
||||
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)
|
||||
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)
|
||||
if 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))
|
||||
|
||||
if reference_obj and ifc.is_moved(reference_obj):
|
||||
|
||||
@@ -6,7 +6,8 @@ Running tests
|
||||
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
|
||||
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
|
||||
test coverage to be 100% where it is technically possible to test.
|
||||
@@ -39,8 +40,8 @@ similar.
|
||||
Tool tests
|
||||
----------
|
||||
|
||||
The tool layer tests actual concrete functions. These have the following
|
||||
dependencies:
|
||||
The tool layer tests actual concrete functions. You will need to install the
|
||||
following dependencies:
|
||||
|
||||
* pytest-blender, accessible to 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``
|
||||
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
|
||||
of all concrete functions.
|
||||
|
||||
|
||||
@@ -16,16 +16,40 @@
|
||||
# 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/>.
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
print("Here are the detected system paths:")
|
||||
print(sys.path)
|
||||
|
||||
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", "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"])
|
||||
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pytest-bdd"])
|
||||
subprocess.call([py_exec, "-m", "pip", "install", f"--target={str(lib)}", "pygments"])
|
||||
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
|
||||
|
||||
sys_paths = [p for p in sys.path if "site-packages" in p]
|
||||
if sys_paths:
|
||||
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
|
||||
|
||||
|
||||
@@ -99,6 +99,15 @@ def i_add_a_sun():
|
||||
def i_add_a_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}"'))
|
||||
@when(parsers.parse('the material "{name}" colour is set to "{colour}"'))
|
||||
|
||||
@@ -223,7 +223,7 @@ class TestAddDrawing:
|
||||
ifc.run(
|
||||
"group.edit_group", group="group", attributes={"Name": "name", "ObjectType": "DRAWING"}
|
||||
).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()
|
||||
ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset")
|
||||
ifc.run(
|
||||
@@ -293,7 +293,7 @@ class TestAddAnnotation:
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
).should_be_called().will_return("element")
|
||||
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()
|
||||
drawing.enable_editing("obj").should_be_called()
|
||||
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
|
||||
|
||||
@@ -222,7 +222,7 @@ class TestAssign(NewFile):
|
||||
tool.Ifc.link(element, element_obj)
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
|
||||
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)
|
||||
assert element_obj.users_collection[0].name == "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)
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
|
||||
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)
|
||||
assert element_obj.users_collection[0].name == "IfcGroup/Unnamed"
|
||||
assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed")
|
||||
|
||||
@@ -260,7 +260,7 @@ class TestGetDrawingGroup(NewFile):
|
||||
tool.Ifc.set(ifc)
|
||||
element = ifc.createIfcAnnotation()
|
||||
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
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestGetGroupElements(NewFile):
|
||||
tool.Ifc.set(ifc)
|
||||
element = ifc.createIfcAnnotation()
|
||||
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,)
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ import ifcopenshell.api
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {}
|
||||
self.settings = {
|
||||
"Name": "Unnamed",
|
||||
"Description": "",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
@@ -33,6 +36,7 @@ class Usecase:
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"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(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatedObjects": self.settings["product"],
|
||||
"RelatingGroup": self.settings["group"],
|
||||
}
|
||||
)
|
||||
rel = self.settings["group"].IsGroupedBy[0]
|
||||
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)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"group": None, "attributes": {}}
|
||||
self.settings = {
|
||||
"group": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
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 []
|
||||
elif self.settings["pset"].is_a("IfcQuantitySet"):
|
||||
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:
|
||||
self.file.remove(prop)
|
||||
self.file.remove(self.settings["pset"])
|
||||
|
||||
@@ -189,7 +189,7 @@ class Selector:
|
||||
value = None
|
||||
for element in elements:
|
||||
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
|
||||
if comparison and cls.filter_element(element, element_value, comparison, value):
|
||||
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("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):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
|
||||
@@ -1111,7 +1111,7 @@ class TestIdsAuthoring(unittest.TestCase):
|
||||
group = ifcopenshell.api.run("group.add_group", ifc)
|
||||
facet = ids.partOf.create(entity="IfcGroup")
|
||||
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)
|
||||
|
||||
# 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")
|
||||
group = ifc.createIfcInventory()
|
||||
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)
|
||||
|
||||
# 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*="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):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
element.Name = "Foobar"
|
||||
|
||||
@@ -42,6 +42,7 @@ def cast_to_value(from_value, to_value):
|
||||
|
||||
class Facet:
|
||||
def __init__(self, *parameters):
|
||||
self.status = None
|
||||
self.failed_entities = []
|
||||
self.failed_reasons = []
|
||||
for i, name in enumerate(self.parameters):
|
||||
|
||||
@@ -217,7 +217,8 @@ class Specification:
|
||||
self.applicable_entities.append(element)
|
||||
for facet in self.requirements:
|
||||
result = facet(element)
|
||||
if not bool(result):
|
||||
facet.status = bool(result)
|
||||
if not facet.status:
|
||||
self.failed_entities.add(element)
|
||||
facet.failed_entities.append(element)
|
||||
facet.failed_reasons.append(str(result))
|
||||
@@ -227,5 +228,7 @@ class Specification:
|
||||
self.status = False
|
||||
elif self.minOccurs != 0 and not self.applicable_entities:
|
||||
self.status = False
|
||||
for facet in self.requirements:
|
||||
facet.status = False
|
||||
elif len(self.applicable_entities) > (self.maxOccurs or 1):
|
||||
self.status = False
|
||||
|
||||
@@ -28,10 +28,10 @@ class Reporter:
|
||||
def report(self, ids):
|
||||
pass
|
||||
|
||||
def to_string():
|
||||
def to_string(self):
|
||||
return ""
|
||||
|
||||
def write(filepath):
|
||||
def write(self, filepath):
|
||||
pass
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class Json(Reporter):
|
||||
requirements.append(
|
||||
{
|
||||
"description": requirement.to_string("requirement"),
|
||||
"success": not requirement.failed_entities,
|
||||
"status": requirement.status,
|
||||
"failed_entities": [
|
||||
{"reason": requirement.failed_reasons[i], "element": str(e)}
|
||||
for i, e in enumerate(requirement.failed_entities[0:10])
|
||||
|
||||
@@ -1157,7 +1157,7 @@ class TestPartOf:
|
||||
group = ifcopenshell.api.run("group.add_group", ifc)
|
||||
facet = PartOf(entity="IfcGroup")
|
||||
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)
|
||||
|
||||
# An IfcGroup can be passed by subtypes
|
||||
@@ -1165,7 +1165,7 @@ class TestPartOf:
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly")
|
||||
group = ifc.createIfcInventory()
|
||||
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)
|
||||
|
||||
# An IfcSystem only checks that a system is assigned without any other logic
|
||||
|
||||
@@ -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()
|
||||
@@ -10,6 +10,7 @@ class IDSContainer extends HTMLElement {
|
||||
this.filename = 'specifications.ids';
|
||||
this.ids = null;
|
||||
this.containerId = crypto.randomUUID();
|
||||
this.isEditing = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +316,13 @@ class IDSFacets extends HTMLElement {
|
||||
}
|
||||
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 {
|
||||
@@ -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) {
|
||||
for (var i=0; i<templates.length; i++) {
|
||||
var hasKeys = true;
|
||||
@@ -747,6 +770,13 @@ class IDSSpecs extends HTMLElement {
|
||||
}
|
||||
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 {
|
||||
@@ -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) {
|
||||
// The HTML draggable API is terrible.
|
||||
e.preventDefault();
|
||||
@@ -809,7 +848,7 @@ class IDSLoader extends HTMLElement {
|
||||
inputElement.accept = '.ids,.xml';
|
||||
inputElement.multiple = false;
|
||||
inputElement.addEventListener("change", this.loadFile)
|
||||
inputElement.dispatchEvent(new MouseEvent("click"));
|
||||
inputElement.dispatchEvent(new MouseEvent("click"));
|
||||
}
|
||||
|
||||
loadFile(e) {
|
||||
@@ -845,9 +884,8 @@ class IDSSave extends HTMLElement {
|
||||
}
|
||||
|
||||
click() {
|
||||
var xmlString = new XMLSerializer().serializeToString(this.closest('ids-container').ids);
|
||||
console.log(xmlString);
|
||||
var container = this.closest('ids-container')
|
||||
var xmlString = new XMLSerializer().serializeToString(container.ids);
|
||||
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-loader', IDSLoader);
|
||||
window.customElements.define('ids-save', IDSSave);
|
||||
window.customElements.define('ids-audit', IDSAudit);
|
||||
window.customElements.define('ids-info', IDSInfo);
|
||||
window.customElements.define('ids-info-element', IDSInfoElement);
|
||||
window.customElements.define('ids-specs', IDSSpecs);
|
||||
|
||||
@@ -47,9 +47,11 @@
|
||||
<i data-feather="download"></i> SAVE
|
||||
</a>
|
||||
</ids-save>
|
||||
<a href="#">
|
||||
<i data-feather="play"></i> AUDIT MODEL
|
||||
</a>
|
||||
<ids-audit>
|
||||
<a href="#">
|
||||
<i data-feather="play"></i> AUDIT MODEL
|
||||
</a>
|
||||
</ids-audit>
|
||||
<span>
|
||||
<a href="#">
|
||||
<i data-feather="x"></i> CLOSE
|
||||
@@ -139,6 +141,13 @@
|
||||
<i data-feather="x"></i>
|
||||
</ids-facet-remove>
|
||||
</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-instructions class="requirement-instructions" title="Instructions">
|
||||
Optionally write instructions about how to achieve this requirement.
|
||||
@@ -163,7 +172,7 @@
|
||||
</ids-spec-move>
|
||||
</span>
|
||||
</div>
|
||||
<ids-spec>
|
||||
</ids-spec>
|
||||
</template>
|
||||
</ids-specs>
|
||||
</div>
|
||||
|
||||
@@ -270,20 +270,30 @@ ids-spec ids-spec-handle .snippet {
|
||||
bottom: -5px;
|
||||
position: relative;
|
||||
}
|
||||
.result-icon {
|
||||
ids-result {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
margin-left: -30px;
|
||||
margin-top: -2px;
|
||||
margin-top: -3px;
|
||||
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);
|
||||
}
|
||||
.result-icon.fail {
|
||||
ids-result[name="fail"] {
|
||||
color: var(--red);
|
||||
}
|
||||
.result-icon:hover {
|
||||
ids-result:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
.hidden {
|
||||
|
||||
Reference in New Issue
Block a user