mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 18:16:20 +00:00
Refactoring of BIMTester Module (#1259)
Co-authored-by: Ihab El Aghoury <ihab_elaghoury@yahoo.ca>
This commit is contained in:
@@ -17,6 +17,7 @@ if bpy is not None:
|
|||||||
"context": None,
|
"context": None,
|
||||||
"covetool": None,
|
"covetool": None,
|
||||||
"csv": None,
|
"csv": None,
|
||||||
|
"bimtester": None,
|
||||||
"debug": None,
|
"debug": None,
|
||||||
"geometry": None,
|
"geometry": None,
|
||||||
"model": None,
|
"model": None,
|
||||||
@@ -37,7 +38,6 @@ if bpy is not None:
|
|||||||
operator.SelectClass,
|
operator.SelectClass,
|
||||||
operator.SelectType,
|
operator.SelectType,
|
||||||
operator.OpenUri,
|
operator.OpenUri,
|
||||||
operator.SelectFeaturesDir,
|
|
||||||
operator.SelectDiffJsonFile,
|
operator.SelectDiffJsonFile,
|
||||||
operator.SelectDiffNewFile,
|
operator.SelectDiffNewFile,
|
||||||
operator.SelectDiffOldFile,
|
operator.SelectDiffOldFile,
|
||||||
@@ -47,14 +47,8 @@ if bpy is not None:
|
|||||||
operator.ValidateIfcFile,
|
operator.ValidateIfcFile,
|
||||||
operator.ExportIFC,
|
operator.ExportIFC,
|
||||||
operator.ImportIFC,
|
operator.ImportIFC,
|
||||||
operator.ColourByClass,
|
|
||||||
operator.ColourByAttribute,
|
operator.ColourByAttribute,
|
||||||
operator.ColourByPset,
|
operator.ColourByPset,
|
||||||
operator.ResetObjectColours,
|
|
||||||
operator.ApproveClass,
|
|
||||||
operator.RejectClass,
|
|
||||||
operator.SelectAudited,
|
|
||||||
operator.RejectElement,
|
|
||||||
operator.SelectExternalMaterialDir,
|
operator.SelectExternalMaterialDir,
|
||||||
operator.AddSweptSolid,
|
operator.AddSweptSolid,
|
||||||
operator.RemoveSweptSolid,
|
operator.RemoveSweptSolid,
|
||||||
@@ -153,8 +147,6 @@ if bpy is not None:
|
|||||||
operator.PropagateTextData,
|
operator.PropagateTextData,
|
||||||
operator.ConvertLocalToGlobal,
|
operator.ConvertLocalToGlobal,
|
||||||
operator.ConvertGlobalToLocal,
|
operator.ConvertGlobalToLocal,
|
||||||
operator.ExecuteBIMTester,
|
|
||||||
operator.BIMTesterPurge,
|
|
||||||
operator.SelectIfcPatchInput,
|
operator.SelectIfcPatchInput,
|
||||||
operator.SelectIfcPatchOutput,
|
operator.SelectIfcPatchOutput,
|
||||||
operator.ExecuteIfcPatch,
|
operator.ExecuteIfcPatch,
|
||||||
@@ -252,7 +244,6 @@ if bpy is not None:
|
|||||||
ui.BIM_PT_constraints,
|
ui.BIM_PT_constraints,
|
||||||
ui.BIM_PT_search,
|
ui.BIM_PT_search,
|
||||||
ui.BIM_PT_ifcclash,
|
ui.BIM_PT_ifcclash,
|
||||||
ui.BIM_PT_qa,
|
|
||||||
ui.BIM_PT_library,
|
ui.BIM_PT_library,
|
||||||
ui.BIM_PT_gis,
|
ui.BIM_PT_gis,
|
||||||
ui.BIM_PT_presentation_layers,
|
ui.BIM_PT_presentation_layers,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import bpy
|
||||||
|
from . import ui, prop, operator
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
operator.ExecuteBIMTester,
|
||||||
|
operator.BIMTesterPurge,
|
||||||
|
operator.SelectFeaturesDir,
|
||||||
|
operator.RejectElement,
|
||||||
|
operator.ColourByClass,
|
||||||
|
operator.ResetObjectColours,
|
||||||
|
operator.ApproveClass,
|
||||||
|
operator.RejectClass,
|
||||||
|
operator.SelectAudited,
|
||||||
|
prop.BimTesterProperties,
|
||||||
|
ui.BIM_PT_qa,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bpy.types.Scene.BimTesterProperties = bpy.props.PointerProperty(type=prop.BimTesterProperties)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
del bpy.types.Scene.BimTesterProperties
|
||||||
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import bpy
|
||||||
|
import ifcopenshell
|
||||||
|
import bimtester
|
||||||
|
import os
|
||||||
|
import webbrowser
|
||||||
|
from pathlib import Path
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
|
||||||
|
class ExecuteBIMTester(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.execute_bim_tester"
|
||||||
|
bl_label = "Execute BIMTester"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
filename = os.path.join(
|
||||||
|
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
|
||||||
|
)
|
||||||
|
cwd = os.getcwd()
|
||||||
|
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
|
||||||
|
bimtester.run.run_tests({"feature": filename, "advanced_arguments": None, "console": False})
|
||||||
|
bimtester.reports.generate_report()
|
||||||
|
webbrowser.open(
|
||||||
|
"file://"
|
||||||
|
+ os.path.join(
|
||||||
|
bpy.context.scene.BimTesterProperties.features_dir,
|
||||||
|
"report",
|
||||||
|
bpy.context.scene.BimTesterProperties.features_file + ".feature.html",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
os.chdir(cwd)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class BIMTesterPurge(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.bim_tester_purge"
|
||||||
|
bl_label = "Purge Tests"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
filename = os.path.join(
|
||||||
|
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
|
||||||
|
)
|
||||||
|
cwd = os.getcwd()
|
||||||
|
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
|
||||||
|
bimtester.clean.TestPurger().purge()
|
||||||
|
os.chdir(cwd)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class SelectFeaturesDir(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.select_features_dir"
|
||||||
|
bl_label = "Select Features Directory"
|
||||||
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bpy.context.scene.BimTesterProperties.features_dir = (
|
||||||
|
os.path.dirname(os.path.abspath(self.filepath)) if "." in self.filepath else self.filepath
|
||||||
|
)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
context.window_manager.fileselect_add(self)
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
class RejectElement(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.reject_element"
|
||||||
|
bl_label = "Reject Element"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
lines = []
|
||||||
|
for object in bpy.context.selected_objects:
|
||||||
|
lines.append(
|
||||||
|
" * The element {} should not exist because {}".format(
|
||||||
|
object.BIMObjectProperties.attributes[
|
||||||
|
object.BIMObjectProperties.attributes.find("GlobalId")
|
||||||
|
].string_value,
|
||||||
|
bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
QAHelper.append_to_scenario(lines)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class ColourByClass(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.colour_by_class"
|
||||||
|
bl_label = "Colour by Class"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
colours = cycle(colour_list)
|
||||||
|
ifc_classes = {}
|
||||||
|
for obj in bpy.context.visible_objects:
|
||||||
|
if "/" not in obj.name:
|
||||||
|
continue
|
||||||
|
ifc_class = obj.name.split("/")[0]
|
||||||
|
if ifc_class not in ifc_classes:
|
||||||
|
ifc_classes[ifc_class] = next(colours)
|
||||||
|
obj.color = ifc_classes[ifc_class]
|
||||||
|
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||||
|
area.spaces[0].shading.color_type = "OBJECT"
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class ResetObjectColours(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.reset_object_colours"
|
||||||
|
bl_label = "Reset Colours"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for object in bpy.context.selected_objects:
|
||||||
|
object.color = (1, 1, 1, 1)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class ApproveClass(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.approve_class"
|
||||||
|
bl_label = "Approve Class"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
lines = []
|
||||||
|
for object in bpy.context.selected_objects:
|
||||||
|
index = object.BIMObjectProperties.attributes.find("GlobalId")
|
||||||
|
if index != -1:
|
||||||
|
lines.append(
|
||||||
|
" * The element {} is an {}".format(
|
||||||
|
object.BIMObjectProperties.attributes[index].string_value, object.name.split("/")[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
QAHelper.append_to_scenario(lines)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class RejectClass(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.reject_class"
|
||||||
|
bl_label = "Reject Class"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
lines = []
|
||||||
|
for object in bpy.context.selected_objects:
|
||||||
|
lines.append(
|
||||||
|
" * The element {} is an {}".format(
|
||||||
|
object.BIMObjectProperties.attributes[
|
||||||
|
object.BIMObjectProperties.attributes.find("GlobalId")
|
||||||
|
].string_value,
|
||||||
|
bpy.context.scene.BimTesterProperties.audit_ifc_class,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
QAHelper.append_to_scenario(lines)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class SelectAudited(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.select_audited"
|
||||||
|
bl_label = "Select Audited"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
audited_global_ids = []
|
||||||
|
for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
|
||||||
|
with open(filename, "r") as feature_file:
|
||||||
|
lines = feature_file.readlines()
|
||||||
|
for line in lines:
|
||||||
|
words = line.strip().split()
|
||||||
|
for word in words:
|
||||||
|
if self.is_a_global_id(word):
|
||||||
|
audited_global_ids.append(word)
|
||||||
|
for object in bpy.context.visible_objects:
|
||||||
|
index = object.BIMObjectProperties.attributes.find("GlobalId")
|
||||||
|
if index != -1 and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids:
|
||||||
|
object.select_set(True)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def is_a_global_id(self, word):
|
||||||
|
return word[0] in ["0", "1", "2", "3"] and len(word) == 22
|
||||||
|
|
||||||
|
class QAHelper:
|
||||||
|
@classmethod
|
||||||
|
def append_to_scenario(cls, lines):
|
||||||
|
filename = os.path.join(
|
||||||
|
bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature"
|
||||||
|
)
|
||||||
|
if os.path.exists(filename + "~"):
|
||||||
|
os.remove(filename + "~")
|
||||||
|
os.rename(filename, filename + "~")
|
||||||
|
with open(filename, "w") as destination:
|
||||||
|
with open(filename + "~", "r") as source:
|
||||||
|
is_in_scenario = False
|
||||||
|
for source_line in source:
|
||||||
|
if (
|
||||||
|
"Scenario: " in source_line
|
||||||
|
and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
|
||||||
|
):
|
||||||
|
is_in_scenario = True
|
||||||
|
elif is_in_scenario:
|
||||||
|
for line in lines:
|
||||||
|
destination.write(line + "\n")
|
||||||
|
is_in_scenario = False
|
||||||
|
destination.write(source_line)
|
||||||
|
os.remove(filename + "~")
|
||||||
|
|
||||||
|
colour_list = [
|
||||||
|
(0.651, 0.81, 0.892, 1),
|
||||||
|
(0.121, 0.471, 0.706, 1),
|
||||||
|
(0.699, 0.876, 0.54, 1),
|
||||||
|
(0.199, 0.629, 0.174, 1),
|
||||||
|
(0.983, 0.605, 0.602, 1),
|
||||||
|
(0.89, 0.101, 0.112, 1),
|
||||||
|
(0.989, 0.751, 0.427, 1),
|
||||||
|
(0.986, 0.497, 0.1, 1),
|
||||||
|
(0.792, 0.699, 0.839, 1),
|
||||||
|
(0.414, 0.239, 0.603, 1),
|
||||||
|
(0.993, 0.999, 0.6, 1),
|
||||||
|
(0.693, 0.349, 0.157, 1),
|
||||||
|
]
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.prop import StrProperty
|
||||||
|
from bpy.types import PropertyGroup
|
||||||
|
from bpy.props import (
|
||||||
|
PointerProperty,
|
||||||
|
StringProperty,
|
||||||
|
EnumProperty,
|
||||||
|
BoolProperty,
|
||||||
|
IntProperty,
|
||||||
|
FloatProperty,
|
||||||
|
FloatVectorProperty,
|
||||||
|
CollectionProperty,
|
||||||
|
)
|
||||||
|
|
||||||
|
scenarios_enum = []
|
||||||
|
featuresfiles_enum = []
|
||||||
|
classes_enum = []
|
||||||
|
|
||||||
|
|
||||||
|
def getIfcClasses(self, context): # This is a copy of the one in bim.prop (as it is used in other modules, can be refactored later)
|
||||||
|
global classes_enum
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
if len(classes_enum) < 1 and file:
|
||||||
|
declaration = IfcStore.get_schema().declaration_by_name(self.ifc_product)
|
||||||
|
def get_classes(declaration):
|
||||||
|
results = []
|
||||||
|
if not declaration.is_abstract():
|
||||||
|
results.append(declaration.name())
|
||||||
|
for subtype in declaration.subtypes():
|
||||||
|
results.extend(get_classes(subtype))
|
||||||
|
return results
|
||||||
|
classes = get_classes(declaration)
|
||||||
|
classes_enum.extend([(c, c, "") for c in sorted(classes)])
|
||||||
|
return classes_enum
|
||||||
|
|
||||||
|
|
||||||
|
def getFeaturesFiles(self, context):
|
||||||
|
global featuresfiles_enum
|
||||||
|
if len(featuresfiles_enum) < 1:
|
||||||
|
featuresfiles_enum.clear()
|
||||||
|
for filename in Path(context.scene.BimTesterProperties.features_dir).glob("*.feature"):
|
||||||
|
f = str(filename.stem)
|
||||||
|
featuresfiles_enum.append((f, f, ""))
|
||||||
|
return featuresfiles_enum
|
||||||
|
|
||||||
|
|
||||||
|
def refreshFeaturesFiles(self, context):
|
||||||
|
global featuresfiles_enum
|
||||||
|
featuresfiles_enum.clear()
|
||||||
|
getFeaturesFiles(self, context)
|
||||||
|
|
||||||
|
|
||||||
|
def getScenarios(self, context):
|
||||||
|
global scenarios_enum
|
||||||
|
if len(scenarios_enum) < 1:
|
||||||
|
scenarios_enum.clear()
|
||||||
|
|
||||||
|
if context.scene.BimTesterProperties.features_file != '': # To handle the error when no .feature file exists in the folder
|
||||||
|
filename = os.path.join(
|
||||||
|
context.scene.BimTesterProperties.features_dir, context.scene.BimTesterProperties.features_file + ".feature"
|
||||||
|
)
|
||||||
|
with open(filename, "r") as feature_file:
|
||||||
|
lines = feature_file.readlines()
|
||||||
|
for line in lines:
|
||||||
|
if "Scenario:" in line:
|
||||||
|
s = line.strip()[len("Scenario: ") :]
|
||||||
|
scenarios_enum.append((s, s, ""))
|
||||||
|
return scenarios_enum
|
||||||
|
|
||||||
|
|
||||||
|
def refreshScenarios(self, context):
|
||||||
|
global scenarios_enum
|
||||||
|
scenarios_enum.clear()
|
||||||
|
getScenarios(self, context)
|
||||||
|
|
||||||
|
|
||||||
|
class BimTesterProperties(PropertyGroup):
|
||||||
|
features_dir: StringProperty(default="", name="Features Directory", update=refreshFeaturesFiles)
|
||||||
|
features_file: EnumProperty(items=getFeaturesFiles, name="Features File", update=refreshScenarios)
|
||||||
|
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||||
|
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
|
||||||
|
scenario: EnumProperty(items=getScenarios, name="Scenario")
|
||||||
|
# should_load_from_memory: BoolProperty(default=False, name="Load from Memory") # can be added later to mimic the functionality in the CSV Module
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import Panel
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_qa(Panel):
|
||||||
|
bl_label = "BIMTester Quality Auditing"
|
||||||
|
bl_idname = "BIM_PT_qa"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
bimtester_properties = bpy.context.scene.BimTesterProperties
|
||||||
|
|
||||||
|
layout.label(text="Gherkin Setup:")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(bimtester_properties, "features_dir")
|
||||||
|
row.operator("bim.select_features_dir", icon="FILE_FOLDER", text="")
|
||||||
|
|
||||||
|
if bimtester_properties.features_dir:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(bimtester_properties, "features_file")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(bimtester_properties, "scenario")
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if context.scene.BimTesterProperties.features_file != '': # To handle the error when no .feature file exists in the folder
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.execute_bim_tester")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.bim_tester_purge")
|
||||||
|
|
||||||
|
layout.label(text="Quality Auditing:")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(bimtester_properties, "qa_reject_element_reason")
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.reject_element")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator("bim.colour_by_class")
|
||||||
|
row.operator("bim.reset_object_colours")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(bimtester_properties, "audit_ifc_class")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator("bim.approve_class")
|
||||||
|
row.operator("bim.reject_class")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.select_audited")
|
||||||
@@ -256,25 +256,6 @@ class SelectType(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ColourByClass(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.colour_by_class"
|
|
||||||
bl_label = "Colour by Class"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
colours = cycle(colour_list)
|
|
||||||
ifc_classes = {}
|
|
||||||
for obj in bpy.context.visible_objects:
|
|
||||||
if "/" not in obj.name:
|
|
||||||
continue
|
|
||||||
ifc_class = obj.name.split("/")[0]
|
|
||||||
if ifc_class not in ifc_classes:
|
|
||||||
ifc_classes[ifc_class] = next(colours)
|
|
||||||
obj.color = ifc_classes[ifc_class]
|
|
||||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
|
||||||
area.spaces[0].shading.color_type = "OBJECT"
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class ColourByAttribute(bpy.types.Operator):
|
class ColourByAttribute(bpy.types.Operator):
|
||||||
bl_idname = "bim.colour_by_attribute"
|
bl_idname = "bim.colour_by_attribute"
|
||||||
bl_label = "Colour by Attribute"
|
bl_label = "Colour by Attribute"
|
||||||
@@ -321,98 +302,6 @@ class ColourByPset(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ResetObjectColours(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.reset_object_colours"
|
|
||||||
bl_label = "Reset Colours"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
for object in bpy.context.selected_objects:
|
|
||||||
object.color = (1, 1, 1, 1)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class QAHelper:
|
|
||||||
@classmethod
|
|
||||||
def append_to_scenario(cls, lines):
|
|
||||||
filename = os.path.join(
|
|
||||||
bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature"
|
|
||||||
)
|
|
||||||
if os.path.exists(filename + "~"):
|
|
||||||
os.remove(filename + "~")
|
|
||||||
os.rename(filename, filename + "~")
|
|
||||||
with open(filename, "w") as destination:
|
|
||||||
with open(filename + "~", "r") as source:
|
|
||||||
is_in_scenario = False
|
|
||||||
for source_line in source:
|
|
||||||
if (
|
|
||||||
"Scenario: " in source_line
|
|
||||||
and bpy.context.scene.BIMProperties.scenario == source_line.strip()[len("Scenario: ") :]
|
|
||||||
):
|
|
||||||
is_in_scenario = True
|
|
||||||
elif is_in_scenario:
|
|
||||||
for line in lines:
|
|
||||||
destination.write(line + "\n")
|
|
||||||
is_in_scenario = False
|
|
||||||
destination.write(source_line)
|
|
||||||
os.remove(filename + "~")
|
|
||||||
|
|
||||||
|
|
||||||
class ApproveClass(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.approve_class"
|
|
||||||
bl_label = "Approve Class"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
lines = []
|
|
||||||
for object in bpy.context.selected_objects:
|
|
||||||
index = object.BIMObjectProperties.attributes.find("GlobalId")
|
|
||||||
if index != -1:
|
|
||||||
lines.append(
|
|
||||||
" * The element {} is an {}".format(
|
|
||||||
object.BIMObjectProperties.attributes[index].string_value, object.name.split("/")[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
QAHelper.append_to_scenario(lines)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class RejectClass(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.reject_class"
|
|
||||||
bl_label = "Reject Class"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
lines = []
|
|
||||||
for object in bpy.context.selected_objects:
|
|
||||||
lines.append(
|
|
||||||
" * The element {} is an {}".format(
|
|
||||||
object.BIMObjectProperties.attributes[
|
|
||||||
object.BIMObjectProperties.attributes.find("GlobalId")
|
|
||||||
].string_value,
|
|
||||||
bpy.context.scene.BIMProperties.audit_ifc_class,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
QAHelper.append_to_scenario(lines)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class RejectElement(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.reject_element"
|
|
||||||
bl_label = "Reject Element"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
lines = []
|
|
||||||
for object in bpy.context.selected_objects:
|
|
||||||
lines.append(
|
|
||||||
" * The element {} should not exist because {}".format(
|
|
||||||
object.BIMObjectProperties.attributes[
|
|
||||||
object.BIMObjectProperties.attributes.find("GlobalId")
|
|
||||||
].string_value,
|
|
||||||
bpy.context.scene.BIMProperties.qa_reject_element_reason,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
QAHelper.append_to_scenario(lines)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class OpenUri(bpy.types.Operator):
|
class OpenUri(bpy.types.Operator):
|
||||||
bl_idname = "bim.open_uri"
|
bl_idname = "bim.open_uri"
|
||||||
bl_label = "Open URI"
|
bl_label = "Open URI"
|
||||||
@@ -423,30 +312,6 @@ class OpenUri(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectAudited(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.select_audited"
|
|
||||||
bl_label = "Select Audited"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
audited_global_ids = []
|
|
||||||
for filename in Path(bpy.context.scene.BIMProperties.features_dir).glob("*.feature"):
|
|
||||||
with open(filename, "r") as feature_file:
|
|
||||||
lines = feature_file.readlines()
|
|
||||||
for line in lines:
|
|
||||||
words = line.strip().split()
|
|
||||||
for word in words:
|
|
||||||
if self.is_a_global_id(word):
|
|
||||||
audited_global_ids.append(word)
|
|
||||||
for object in bpy.context.visible_objects:
|
|
||||||
index = object.BIMObjectProperties.attributes.find("GlobalId")
|
|
||||||
if index != -1 and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids:
|
|
||||||
object.select_set(True)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
def is_a_global_id(self, word):
|
|
||||||
return word[0] in ["0", "1", "2", "3"] and len(word) == 22
|
|
||||||
|
|
||||||
|
|
||||||
class AddQto(bpy.types.Operator):
|
class AddQto(bpy.types.Operator):
|
||||||
bl_idname = "bim.add_qto"
|
bl_idname = "bim.add_qto"
|
||||||
bl_label = "Add Qto"
|
bl_label = "Add Qto"
|
||||||
@@ -1341,22 +1206,6 @@ class SelectSmartGroup(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class SelectFeaturesDir(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.select_features_dir"
|
|
||||||
bl_label = "Select Features Directory"
|
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
bpy.context.scene.BIMProperties.features_dir = (
|
|
||||||
os.path.dirname(os.path.abspath(self.filepath)) if "." in self.filepath else self.filepath
|
|
||||||
)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
context.window_manager.fileselect_add(self)
|
|
||||||
return {"RUNNING_MODAL"}
|
|
||||||
|
|
||||||
|
|
||||||
class SelectIfcFile(bpy.types.Operator):
|
class SelectIfcFile(bpy.types.Operator):
|
||||||
bl_idname = "bim.select_ifc_file"
|
bl_idname = "bim.select_ifc_file"
|
||||||
bl_label = "Select IFC File"
|
bl_label = "Select IFC File"
|
||||||
@@ -2720,52 +2569,6 @@ class ConvertGlobalToLocal(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ExecuteBIMTester(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.execute_bim_tester"
|
|
||||||
bl_label = "Execute BIMTester"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
import bimtester
|
|
||||||
import bimtester.run
|
|
||||||
import bimtester.reports
|
|
||||||
|
|
||||||
filename = os.path.join(
|
|
||||||
bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature"
|
|
||||||
)
|
|
||||||
cwd = os.getcwd()
|
|
||||||
os.chdir(bpy.context.scene.BIMProperties.features_dir)
|
|
||||||
bimtester.run.run_tests({"feature": filename, "advanced_arguments": None, "console": False})
|
|
||||||
bimtester.reports.generate_report()
|
|
||||||
webbrowser.open(
|
|
||||||
"file://"
|
|
||||||
+ os.path.join(
|
|
||||||
bpy.context.scene.BIMProperties.features_dir,
|
|
||||||
"report",
|
|
||||||
bpy.context.scene.BIMProperties.features_file + ".feature.html",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
os.chdir(cwd)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class BIMTesterPurge(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.bim_tester_purge"
|
|
||||||
bl_label = "Purge Tests"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
import bimtester
|
|
||||||
import bimtester.clean
|
|
||||||
|
|
||||||
filename = os.path.join(
|
|
||||||
bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature"
|
|
||||||
)
|
|
||||||
cwd = os.getcwd()
|
|
||||||
os.chdir(bpy.context.scene.BIMProperties.features_dir)
|
|
||||||
bimtester.clean.TestPurger().purge()
|
|
||||||
os.chdir(cwd)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class SelectIfcPatchInput(bpy.types.Operator):
|
class SelectIfcPatchInput(bpy.types.Operator):
|
||||||
bl_idname = "bim.select_ifc_patch_input"
|
bl_idname = "bim.select_ifc_patch_input"
|
||||||
bl_label = "Select IFC Patch Input"
|
bl_label = "Select IFC Patch Input"
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ classes_enum = []
|
|||||||
types_enum = []
|
types_enum = []
|
||||||
availablematerialpsets_enum = []
|
availablematerialpsets_enum = []
|
||||||
ifcpatchrecipes_enum = []
|
ifcpatchrecipes_enum = []
|
||||||
featuresfiles_enum = []
|
|
||||||
titleblocks_enum = []
|
titleblocks_enum = []
|
||||||
scenarios_enum = []
|
|
||||||
materialpsetnames_enum = []
|
materialpsetnames_enum = []
|
||||||
psetfiles_enum = []
|
psetfiles_enum = []
|
||||||
psettemplatefiles_enum = []
|
psettemplatefiles_enum = []
|
||||||
@@ -328,23 +326,6 @@ def getIfcPatchRecipes(self, context):
|
|||||||
ifcpatchrecipes_enum.append((f, f, ""))
|
ifcpatchrecipes_enum.append((f, f, ""))
|
||||||
return ifcpatchrecipes_enum
|
return ifcpatchrecipes_enum
|
||||||
|
|
||||||
|
|
||||||
def getFeaturesFiles(self, context):
|
|
||||||
global featuresfiles_enum
|
|
||||||
if len(featuresfiles_enum) < 1:
|
|
||||||
featuresfiles_enum.clear()
|
|
||||||
for filename in Path(context.scene.BIMProperties.features_dir).glob("*.feature"):
|
|
||||||
f = str(filename.stem)
|
|
||||||
featuresfiles_enum.append((f, f, ""))
|
|
||||||
return featuresfiles_enum
|
|
||||||
|
|
||||||
|
|
||||||
def refreshFeaturesFiles(self, context):
|
|
||||||
global featuresfiles_enum
|
|
||||||
featuresfiles_enum.clear()
|
|
||||||
getFeaturesFiles(self, context)
|
|
||||||
|
|
||||||
|
|
||||||
def getTitleblocks(self, context):
|
def getTitleblocks(self, context):
|
||||||
global titleblocks_enum
|
global titleblocks_enum
|
||||||
if len(titleblocks_enum) < 1:
|
if len(titleblocks_enum) < 1:
|
||||||
@@ -380,28 +361,6 @@ def toggleDecorationsOnLoad(*args):
|
|||||||
decoration.DecorationsHandler.uninstall()
|
decoration.DecorationsHandler.uninstall()
|
||||||
|
|
||||||
|
|
||||||
def getScenarios(self, context):
|
|
||||||
global scenarios_enum
|
|
||||||
if len(scenarios_enum) < 1:
|
|
||||||
scenarios_enum.clear()
|
|
||||||
filename = os.path.join(
|
|
||||||
context.scene.BIMProperties.features_dir, context.scene.BIMProperties.features_file + ".feature"
|
|
||||||
)
|
|
||||||
with open(filename, "r") as feature_file:
|
|
||||||
lines = feature_file.readlines()
|
|
||||||
for line in lines:
|
|
||||||
if "Scenario:" in line:
|
|
||||||
s = line.strip()[len("Scenario: ") :]
|
|
||||||
scenarios_enum.append((s, s, ""))
|
|
||||||
return scenarios_enum
|
|
||||||
|
|
||||||
|
|
||||||
def refreshScenarios(self, context):
|
|
||||||
global scenarios_enum
|
|
||||||
scenarios_enum.clear()
|
|
||||||
getScenarios(self, context)
|
|
||||||
|
|
||||||
|
|
||||||
def getPsetTemplateFiles(self, context):
|
def getPsetTemplateFiles(self, context):
|
||||||
global psettemplatefiles_enum
|
global psettemplatefiles_enum
|
||||||
if len(psettemplatefiles_enum) < 1:
|
if len(psettemplatefiles_enum) < 1:
|
||||||
@@ -1226,7 +1185,6 @@ class BIMProperties(PropertyGroup):
|
|||||||
data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory")
|
data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory")
|
||||||
ifc_file: StringProperty(name="IFC File")
|
ifc_file: StringProperty(name="IFC File")
|
||||||
ifc_cache: StringProperty(name="IFC Cache")
|
ifc_cache: StringProperty(name="IFC Cache")
|
||||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
|
||||||
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
|
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
|
||||||
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
|
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
|
||||||
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
|
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
|
||||||
@@ -1267,7 +1225,6 @@ class BIMProperties(PropertyGroup):
|
|||||||
import_should_allow_non_element_aggregates: BoolProperty(name="Import Non-Element Aggregates", default=False)
|
import_should_allow_non_element_aggregates: BoolProperty(name="Import Non-Element Aggregates", default=False)
|
||||||
import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False)
|
import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False)
|
||||||
import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0")
|
import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0")
|
||||||
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
|
|
||||||
|
|
||||||
person: PointerProperty(type=Person)
|
person: PointerProperty(type=Person)
|
||||||
active_person_id: IntProperty(name="Active Person Id")
|
active_person_id: IntProperty(name="Active Person Id")
|
||||||
@@ -1290,9 +1247,6 @@ class BIMProperties(PropertyGroup):
|
|||||||
search_pset_name: StringProperty(name="Search Pset Name")
|
search_pset_name: StringProperty(name="Search Pset Name")
|
||||||
search_prop_name: StringProperty(name="Search Prop Name")
|
search_prop_name: StringProperty(name="Search Prop Name")
|
||||||
search_pset_value: StringProperty(name="Search Pset Value")
|
search_pset_value: StringProperty(name="Search Pset Value")
|
||||||
features_dir: StringProperty(default="", name="Features Directory", update=refreshFeaturesFiles)
|
|
||||||
features_file: EnumProperty(items=getFeaturesFiles, name="Features File", update=refreshScenarios)
|
|
||||||
scenario: EnumProperty(items=getScenarios, name="Scenario")
|
|
||||||
diff_json_file: StringProperty(default="", name="Diff JSON File")
|
diff_json_file: StringProperty(default="", name="Diff JSON File")
|
||||||
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
||||||
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
||||||
|
|||||||
@@ -1069,63 +1069,6 @@ class BIM_PT_search(Panel):
|
|||||||
row.operator("bim.colour_by_pset", text="", icon="BRUSH_DATA")
|
row.operator("bim.colour_by_pset", text="", icon="BRUSH_DATA")
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_qa(Panel):
|
|
||||||
bl_label = "BIMTester Quality Auditing"
|
|
||||||
bl_idname = "BIM_PT_qa"
|
|
||||||
bl_options = {"DEFAULT_CLOSED"}
|
|
||||||
bl_space_type = "PROPERTIES"
|
|
||||||
bl_region_type = "WINDOW"
|
|
||||||
bl_context = "scene"
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
|
|
||||||
scene = context.scene
|
|
||||||
bim_properties = bpy.context.scene.BIMProperties
|
|
||||||
|
|
||||||
layout.label(text="Gherkin Setup:")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(bim_properties, "features_dir")
|
|
||||||
row.operator("bim.select_features_dir", icon="FILE_FOLDER", text="")
|
|
||||||
|
|
||||||
if bim_properties.features_dir:
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(bim_properties, "features_file")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(bim_properties, "scenario")
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.operator("bim.execute_bim_tester")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.operator("bim.bim_tester_purge")
|
|
||||||
|
|
||||||
layout.label(text="Quality Auditing:")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(bim_properties, "qa_reject_element_reason")
|
|
||||||
row = layout.row()
|
|
||||||
row.operator("bim.reject_element")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.operator("bim.colour_by_class")
|
|
||||||
row.operator("bim.reset_object_colours")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(bim_properties, "audit_ifc_class")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.operator("bim.approve_class")
|
|
||||||
row.operator("bim.reject_class")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.operator("bim.select_audited")
|
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_library(Panel):
|
class BIM_PT_library(Panel):
|
||||||
bl_label = "IFC BIM Server Library"
|
bl_label = "IFC BIM Server Library"
|
||||||
|
|||||||
Reference in New Issue
Block a user