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

This commit is contained in:
Carlos Dias
2023-08-25 08:25:25 -03:00
committed by GitHub
92 changed files with 2684 additions and 942 deletions
+57
View File
@@ -0,0 +1,57 @@
name: ci-bcf-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
push:
paths:
- '.github/workflows/ci-bcf-pypi.yml'
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2 # https://github.com/actions/checkout
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
pip install build
cd src/bcf &&
python -m build
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/bcf/dist
-37
View File
@@ -1,37 +0,0 @@
name: ci-bcf
on:
push:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell' &&
contains(github.event.head_commit.message, '[bcf release]')
steps:
- run: echo ok go
upload:
needs: activate
name: Upload BCF package to Pypi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Build package
run: |
cd src/bcf
pip install build
python -m build
- name: Test
run: |
cd src/bcf
make test
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
packages_dir: src/bcf/dist
+4 -1
View File
@@ -146,7 +146,10 @@ classes = [
# Structural analysis
ui.BIM_PT_tab_structural,
# Construction scheduling
ui.BIM_PT_tab_4D5D,
ui.BIM_PT_tab_status,
ui.BIM_PT_tab_resources,
ui.BIM_PT_tab_cost,
ui.BIM_PT_tab_sequence,
# Facility management
ui.BIM_PT_tab_handover,
ui.BIM_PT_tab_operations,
@@ -5,27 +5,27 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#18=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#19=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Status.ifc','2020-01-01T00:00:00',(),(),'EPset_Status','EPset_Status',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3JoyTt$cz4fhYuQvJeTMhs',$,'EPset_Status','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProduct',(#2,#4));
#2= IFCSIMPLEPROPERTYTEMPLATE('2HBOdOfz5E3hZ7IBLCq$$D',$,'Status','Status of the element, predominately used in renovation or retrofitting projects. The status can be assigned to as "New" - element designed as new addition, "Existing" - element exists and remains, "Demolish" - element existed but is to be demolished, "Temporary" - element will exists only temporary (like a temporary support structure).',.P_ENUMERATEDVALUE.,'IfcLabel',$,#3,$,$,$,.READWRITE.);
#3= IFCPROPERTYENUMERATION('EPEnum_ElementStatus',(IFCLABEL('NEW'),IFCLABEL('EXISTING'),IFCLABEL('DEMOLISH'),IFCLABEL('TEMPORARY'),IFCLABEL('OTHER'),IFCLABEL('NOTKNOWN'),IFCLABEL('UNSET')),$);
#4=IFCSIMPLEPROPERTYTEMPLATE('3ttXxqmlz62BlxjzEPpqvH',$,'UserDefinedStatus','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
+5 -2
View File
@@ -82,7 +82,7 @@ class IfcExporter:
)
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
self.get_application_name(), self.get_application_version()
self.get_application_name(), tool.Blender.get_blenderbim_version()
)
def sync_all_objects(self):
@@ -162,7 +162,7 @@ class IfcExporter:
return "BlenderBIM"
def get_application_version(self):
return ".".join(
version = ".".join(
[
str(x)
for x in [
@@ -172,6 +172,9 @@ class IfcExporter:
][0]
]
)
if blenderbim.bim.last_commit_hash != "8888888":
version += f"-{blenderbim.bim.last_commit_hash[:7]}"
return version
class IfcExportSettings:
+26 -6
View File
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from mathutils import Vector
from math import cos, degrees
@@ -73,17 +74,27 @@ def name_callback(obj, data):
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
if not obj.BIMObjectProperties.ifc_definition_id:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if "/" in obj.name:
object_name = obj.name
element_name = obj.name.split("/", 1)[1]
else:
element_name = obj.name
object_name = element.is_a() + f"/{element_name}"
obj.name = object_name # NOTE: doesn't trigger infinite recursion
if element.is_a("IfcGridAxis"):
element.AxisTag = obj.name.split("/")[1]
element.AxisTag = object_name.split("/")[1]
refresh_ui_data()
if not element.is_a("IfcRoot"):
return
element.Name = element_name
if obj.BIMObjectProperties.collection:
obj.BIMObjectProperties.collection.name = obj.name
element.Name = "/".join(obj.name.split("/")[1:])
obj.BIMObjectProperties.collection.name = object_name
refresh_ui_data()
@@ -106,7 +117,7 @@ def update_bim_tool_props():
return
mode = bpy.context.mode
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
if not current_tool or current_tool.idname != "bim.bim_tool":
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
return
element = tool.Ifc.get_entity(obj)
if not element:
@@ -114,6 +125,16 @@ def update_bim_tool_props():
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
props = bpy.context.scene.BIMModelProperties
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
is_bim_tool = current_tool.idname == "bim.bim_tool"
if is_bim_tool:
props.ifc_class = element_type.is_a()
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
props.relating_type_id = str(element_type.id())
extrusion = tool.Model.get_extrusion(representation)
if not extrusion:
return
@@ -124,7 +145,6 @@ def update_bim_tool_props():
return x_angle
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props = bpy.context.scene.BIMModelProperties
if not AuthoringData.is_loaded:
AuthoringData.load()
+4 -2
View File
@@ -277,6 +277,8 @@ def draw_filter(layout, props, data, module):
if not data.is_loaded:
data.load()
sprops = bpy.context.scene.BIMSearchProperties
if tool.Ifc.get():
row = layout.row(align=True)
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
@@ -293,9 +295,9 @@ def draw_filter(layout, props, data, module):
box = layout.box()
row = box.row(align=True)
row.prop(props, "facet", text="")
row.prop(sprops, "facet", text="")
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
op.type = props.facet
op.type = sprops.facet
op.index = i
op.module = module
+8 -4
View File
@@ -221,7 +221,9 @@ class IfcStore:
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
if IfcStore.history:
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
@@ -248,7 +250,9 @@ class IfcStore:
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@@ -321,7 +325,7 @@ class IfcStore:
IfcStore.begin_transaction(operator)
if tool.Ifc.get():
tool.Ifc.get().begin_transaction()
if BrickStore.graph:
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
BrickStore.begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
@@ -339,7 +343,7 @@ class IfcStore:
IfcStore.add_transaction_operation(
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
)
if BrickStore.graph:
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
BrickStore.end_transaction()
IfcStore.end_transaction(operator)
blenderbim.bim.handler.refresh_ui_data()
+26 -25
View File
@@ -227,16 +227,16 @@ class IfcImporter:
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_curve = ifcopenshell.geom.settings()
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
self.settings_body_2d = ifcopenshell.geom.settings()
self.settings_body_2d.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings_body_2d.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings_body_2d.set(self.settings_body_2d.STRICT_TOLERANCE, True)
self.settings_body_2d.set(self.settings_body_2d.INCLUDE_CURVES, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
self.settings_plan_2d = ifcopenshell.geom.settings()
self.settings_plan_2d.set(self.settings_plan_2d.INCLUDE_CURVES, True)
self.settings_plan_2d.set(self.settings_plan_2d.STRICT_TOLERANCE, True)
self.project = None
self.has_existing_project = False
self.collections = {}
@@ -348,13 +348,14 @@ class IfcImporter:
if c.ContextIdentifier in ["Body", "Facetation"]
]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if not self.body_contexts:
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
# Annotation ContextType is to accommodate broken Revit files
@@ -365,7 +366,7 @@ class IfcImporter:
if c.ContextType in ["Plan", "Annotation"] or c.ContextIdentifier == "Annotation"
]
if self.plan_contexts:
self.settings_2d.set_context_ids(self.plan_contexts)
self.settings_plan_2d.set_context_ids(self.plan_contexts)
def process_element_filter(self):
offset = self.ifc_import_settings.element_offset
@@ -645,7 +646,7 @@ class IfcImporter:
self.ifc_import_settings.logger.error("An invalid grid was found %s", grid)
continue
if grid.Representation:
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, grid)
grid_obj = self.create_product(grid, shape)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True)
@@ -664,7 +665,7 @@ class IfcImporter:
def create_grid_axes(self, axes, grid_collection, grid_obj):
for axis in axes:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
@@ -698,7 +699,7 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings, representation)
except:
try:
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, representation)
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
if shape:
@@ -764,9 +765,9 @@ class IfcImporter:
if self.ifc_import_settings.should_load_geometry:
products = self.create_products(elements)
elements -= products
products = self.create_products(elements, settings=self.settings_curve)
products = self.create_products(elements, settings=self.settings_body_2d)
elements -= products
products = self.create_products(elements, settings=self.settings_2d)
products = self.create_products(elements, settings=self.settings_plan_2d)
elements -= products
products = self.create_pointclouds(elements)
elements -= products
@@ -901,10 +902,10 @@ class IfcImporter:
self.structural_collection.children.link(self.structural_connection_collection)
self.project["blender"].children.link(self.structural_collection)
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_plan_2d)
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_plan_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_plan_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_plan_2d)
self.create_structural_point_connections()
def create_structural_point_connections(self):
@@ -39,8 +39,12 @@ classes = (
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
ui.BIM_PT_ifc_brickschema_references,
ui.BIM_PT_brickschema_project_info,
ui.BIM_PT_brickschema_namespaces,
ui.BIM_PT_brickschema_create_entity,
ui.BIM_PT_brickschema_viewport,
ui.BIM_UL_bricks,
ui.BIM_PT_ifc_brickschema_references,
)
@@ -46,7 +46,7 @@ class BrickschemaData:
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
return BrickStore.graph is not None # `if BrickStore.graph` by itself takes ages.
@classmethod
def active_relations(cls):
@@ -67,13 +67,17 @@ class BrickschemaData:
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?predicate ?object ?sp ?sv WHERE {
<{uri}> ?predicate ?object .
OPTIONAL {
{ ?predicate rdfs:range brick:TimeseriesReference . }
UNION
{ ?predicate a brick:EntityProperty . }
?object ?sp ?sv }
SELECT DISTINCT ?predicate ?object ?label ?sp ?sv WHERE {
<{uri}> ?predicate ?object .
OPTIONAL {
?object rdfs:label ?label .
}
OPTIONAL {
{ ?predicate rdfs:range brick:TimeseriesReference . }
UNION
{ ?predicate a brick:EntityProperty . }
?object ?sp ?sv .
}
}
GROUP BY ?object
""".replace(
@@ -84,14 +88,15 @@ class BrickschemaData:
predicate_uri = row.get("predicate")
predicate_name = predicate_uri.toPython().split("#")[-1]
object_uri = row.get("object")
print("DEBUG: object is ", object_uri)
if isinstance(object_uri, BNode):
object_name = "[]"
else:
try:
object_name = object_uri.toPython().split("#")[-1]
except:
object_name = str(object_uri)
object_name = row.get("label")
if not object_name:
if isinstance(object_uri, BNode):
object_name = "[]"
else:
try:
object_name = object_uri.toPython().split("#")[-1]
except:
object_name = str(object_uri)
results.append(
{
"predicate_uri": predicate_uri,
@@ -99,7 +104,7 @@ class BrickschemaData:
"object_uri": object_uri,
"object_name": object_name,
"is_uri": isinstance(object_uri, URIRef),
"is_globalid": predicate_uri == "globalID",
"is_globalid": predicate_name == "ifcGlobalID",
}
)
if isinstance(object_uri, BNode):
@@ -109,7 +114,6 @@ class BrickschemaData:
object2_name = object2.toPython().split("#")[-1]
except:
object2_name = str(object2)
print("DEBUG: ", predicate_name, ":", predicate2_name)
results.append(
{
"predicate_uri": None,
@@ -117,10 +121,9 @@ class BrickschemaData:
"object_uri": object2,
"object_name": object2_name,
"is_uri": isinstance(object2, URIRef),
"is_globalid": predicate2_name == "globalID",
"is_globalid": predicate2_name == "ifcGlobalID",
}
)
print("")
return results
@@ -146,7 +149,7 @@ class BrickschemaReferencesData:
for library in ifc.by_type("IfcLibraryInformation"):
if tool.Ifc.get_schema() == "IFC2X3":
results.append((str(library.id()), library.Name or "Unnamed", ""))
elif ".ttl" in library.Location:
elif library.Location and ".ttl" in library.Location:
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results
@@ -105,6 +105,7 @@ class ConvertBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.convert_brick_project"
bl_label = "Convert Brick Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create an Ifc library for this Brick project"
def _execute(self, context):
core.convert_brick_project(tool.Ifc, tool.Brick)
@@ -114,9 +115,18 @@ class AssignBrickReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_brick_reference"
bl_label = "Assign Brick Reference"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Assign the selected Ifc entity to the selected Brick entity"
def _execute(self, context):
if not context.active_object:
self.report({'ERROR'}, f'No Ifc selected')
return
props = context.scene.BIMBrickProperties
try:
props.bricks[props.active_brick_index]
except:
self.report({'ERROR'}, f'No Brick selected')
return
core.assign_brick_reference(
tool.Ifc,
tool.Brick,
@@ -159,7 +169,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
elif props.split_screen_toggled:
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
else:
object = props.new_brick_relation_namespace + props.new_brick_relation_object
object = props.namespace + props.new_brick_relation_object
core.add_brick_relation(
tool.Brick,
brick_uri=brick.uri,
@@ -172,6 +182,7 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
bl_idname = "bim.convert_ifc_to_brick"
bl_label = "Convert IFC To Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert Ifc entities and relations to Brick entities and relations"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -222,7 +233,6 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick)
core.refresh_brick_viewer(tool.Brick, split_screen=True)
class RemoveBrick(bpy.types.Operator, Operator):
@@ -278,7 +288,7 @@ class AddBrickNamespace(bpy.types.Operator, Operator):
props = context.scene.BIMBrickProperties
alias = props.new_brick_namespace_alias
uri = props.new_brick_namespace_uri
core.add_namespace(tool.Brick, alias=alias, uri=uri)
core.add_brick_namespace(tool.Brick, alias=alias, uri=uri)
class RemoveBrickRelation(bpy.types.Operator, Operator):
@@ -45,12 +45,12 @@ def get_libraries(self, context):
def get_namespaces(self, context):
return BrickStore.namespaces
return [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
def get_brick_entity_classes(self, context):
entity = self.brick_entity_create_type
return BrickStore.entity_classes[entity]
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
def get_brick_roots(self, context):
@@ -58,13 +58,13 @@ def get_brick_roots(self, context):
def get_brick_relations(self, context):
def is_label(relation):
return relation["predicate_name"] == "label"
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
new_relations = BrickStore.relationships.copy()
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return new_relations
return BrickStore.relationships
relations = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
for relation in BrickschemaData.data["active_relations"]:
if relation["predicate_name"] == "label":
return relations
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return relations
def update_view(self, context):
@@ -93,7 +93,6 @@ class BIMBrickProperties(PropertyGroup):
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
# namespace manager
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
# create brick entity
@@ -104,9 +103,8 @@ class BIMBrickProperties(PropertyGroup):
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
add_brick_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
# create relations split screen
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
+131 -73
View File
@@ -34,8 +34,19 @@ class BIM_PT_brickschema(Panel):
def draw(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
self.props = context.scene.BIMBrickProperties
class BIM_PT_brickschema_project_info(Panel):
bl_label = "Project Info"
bl_idname = "BIM_PT_brickschema_project_info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
def draw(self, context):
self.props = context.scene.BIMBrickProperties
if not BrickschemaData.data["is_loaded"]:
row = self.layout.row(align=True)
row.operator("bim.new_brick_file", text="Create Project")
@@ -61,48 +72,94 @@ class BIM_PT_brickschema(Panel):
op.should_save_as = True
row.operator("bim.close_brick_project", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
if self.props.brick_settings_toggled:
box = self.layout.box()
row = box.row(align=True)
row.label(text="Active Namespace:")
class BIM_PT_brickschema_namespaces(Panel):
bl_label = "Namespaces"
bl_idname = "BIM_PT_brickschema_namespaces"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
row = box.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
@classmethod
def poll(cls, context):
return BrickStore.graph != None
row = box.row(align=True)
row.label(text="Bind New Namespace:")
row = box.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
def draw(self, context):
self.props = context.scene.BIMBrickProperties
row = self.layout.row(align=True)
row.label(text="Create Entity:")
row.label(text="Active Namespace:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="")
prop_with_search(row, self.props, "namespace", text="")
# hide this if selected entity already has a reference
row = self.layout.row(align=True)
row.label(text="Bind New Namespace:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
class BIM_PT_brickschema_create_entity(Panel):
bl_label = "Create Entity"
bl_idname = "BIM_PT_brickschema_create_entity"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return BrickStore.graph != None
def draw(self, context):
self.props = context.scene.BIMBrickProperties
# TO DO: hide this if selected entity already has a reference, or something similar
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="Class")
row = self.layout.row(align=True)
prop_with_search(row, self.props, "brick_entity_class", text="Type")
row = self.layout.row(align=True)
active = tool.Ifc.get_entity(context.active_object)
if active and context.selected_objects:
row.label(text="Label:")
row.label(text=active.Name if active.Name else "Unnamed")
else:
row.prop(data=self.props, property="new_brick_label", text="")
prop_with_search(row, self.props, "brick_entity_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row.prop(data=self.props, property="new_brick_label", text="Label")
row = self.layout.row(align=True)
col = row.column()
col.alignment = "RIGHT"
row.operator("bim.add_brick", text="Create Entity")
class BIM_PT_brickschema_viewport(Panel):
bl_label = "Viewport"
bl_idname = "BIM_PT_brickschema_viewport"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return BrickStore.graph != None
def draw(self, context):
self.props = context.scene.BIMBrickProperties
row = self.layout.row(align=True)
row.column().alignment = "RIGHT"
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
@@ -141,54 +198,47 @@ class BIM_PT_brickschema(Panel):
if BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
col = row.column()
col.alignment = "RIGHT"
row.column().alignment = "RIGHT"
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
row.operator("bim.remove_brick", text="", icon="X")
if self.props.brick_create_relations_toggled:
if self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
row = self.layout.row(align=True)
row.label(text="Create Relation:")
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
row = self.layout.row(align=True)
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
if split_screen_selection.total_items:
try:
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
except:
split_screen_selection = None
if not split_screen_selection or split_screen_selection.total_items:
row.label(text="No selection", icon="INFO")
else:
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
elif self.props.brick_create_relations_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
if self.props.brick_create_relations_toggled and self.props.add_brick_relation_failed:
row = self.layout.row(align=True)
row.label(text="Failed to find this entity!", icon="ERROR")
for relation in BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
split = self.layout.split(factor=0.85, align=True)
row = split.row(align=True)
row.label(text=relation["predicate_name"])
row.separator()
row.label(text=relation["object_name"])
row = split.row(align=True)
row.column().alignment = "RIGHT"
if self.props.brick_edit_relations_toggled and relation["predicate_uri"] and relation["predicate_name"] != "type":
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
op.predicate = relation["predicate_uri"]
op.object = relation["object_uri"]
if relation["is_uri"] and relation["object_name"] != self.props.active_brick_class:
if relation["is_uri"] and relation["object_uri"].toPython().split("#")[-1] != self.props.active_brick_class:
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
op.item = relation["object_uri"]
if relation["is_globalid"]:
@@ -196,6 +246,23 @@ class BIM_PT_brickschema(Panel):
op.global_id = relation["object_name"]
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
split = layout.split(factor=0.85, align=True)
row = split.row()
label = item.label if item.label else item.name
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
op.split_screen = self.split_screen
row.label(text=label)
if item.total_items:
row = split.row()
row.label(text=str(item.total_items))
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
@@ -219,18 +286,24 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text="No Brickschema Project Loaded")
return
if not BrickschemaReferencesData.data["libraries"]:
if not BrickschemaReferencesData.data["libraries"] and BrickStore.path:
row = self.layout.row(align=True)
if BrickStore.path:
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
else:
row.label(text="No IFC Libraries. Save the Brick project to create a new library.", icon="ERROR")
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
elif not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries: save the Brick project to create a new library", icon="ERROR")
row = self.layout.row(align=True)
row.label(text="Libraries must have location pointing to a \".ttl\" file", icon="INFO")
return
row = self.layout.row(align=True)
prop_with_search(row, self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
if BrickStore.path:
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row(align=True)
row.operator("bim.assign_brick_reference", icon="ADD")
@@ -245,19 +318,4 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
label = item.label if item.label else item.name
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
op.split_screen = self.split_screen
label = label + " (" + str(item.total_items) + ")"
row.label(text=label)
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
@@ -29,7 +29,8 @@ class BIM_PT_cost_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_cost"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -21,13 +21,14 @@ from . import ui, prop, operator
classes = (
operator.AddCsvAttribute,
operator.RemoveCsvAttribute,
operator.RemoveAllCsvAttributes,
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.SelectCsvIfcFile,
operator.ImportCsvAttributes,
operator.ExportCsvAttributes,
operator.ExportIfcCsv,
operator.ImportCsvAttributes,
operator.ImportIfcCsv,
operator.RemoveAllCsvAttributes,
operator.RemoveCsvAttribute,
operator.ReorderCsvAttribute,
operator.SelectCsvIfcFile,
prop.CsvAttribute,
prop.CsvProperties,
ui.BIM_PT_ifccsv,
@@ -62,6 +62,24 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
return {"FINISHED"}
class ReorderCsvAttribute(bpy.types.Operator):
bl_idname = "bim.reorder_csv_attribute"
bl_label = "Reorder CSV Attribute"
bl_options = {"REGISTER", "UNDO"}
old_index: bpy.props.IntProperty()
new_index: bpy.props.IntProperty()
def execute(self, context):
old = context.scene.CsvProperties.csv_attributes[self.old_index]
new = context.scene.CsvProperties.csv_attributes[self.new_index]
props = ["name", "header", "sort", "group", "varies_value", "summary"]
for prop in props:
value = getattr(new, prop)
setattr(new, prop, getattr(old, prop))
setattr(old, prop, value)
return {"FINISHED"}
class ImportCsvAttributes(bpy.types.Operator):
bl_idname = "bim.import_csv_attributes"
bl_label = "Load CSV Settings"
@@ -38,24 +38,24 @@ class CsvAttribute(PropertyGroup):
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
group: EnumProperty(
items=[
("NONE", "None", ""),
("GROUP", "Group", "All rows where this value is identical will be merged."),
("CONCAT", "Concatenation", "Concatenate values if values vary within a group."),
("VARIES", "Varies", "Show a custom value if values vary within a group."),
("SUM", "Sum", "Sums the total value of rows in a group."),
("AVERAGE", "Average", "Averages the total value of rows in a group."),
("MIN", "Min", "Gets the minimum value of rows in a group."),
("MAX", "Max", "Gets the maximum value of rows in a group."),
("NONE", "None", "Don't group any rows"),
("GROUP", "Group", "All rows where this value is identical will be merged"),
("CONCAT", "Concatenation", "Concatenate values if values vary within a group"),
("VARIES", "Varies", "Show a custom value if values vary within a group"),
("SUM", "Sum", "Sums the total value of rows in a group"),
("AVERAGE", "Average", "Averages the total value of rows in a group"),
("MIN", "Min", "Gets the minimum value of rows in a group"),
("MAX", "Max", "Gets the maximum value of rows in a group"),
]
)
varies_value: StringProperty(default="Varies", name="Varies Value")
summary: EnumProperty(
items=[
("NONE", "None", ""),
("SUM", "Sum", "Sums the total value of all rows."),
("AVERAGE", "Average", "Averages the total value of all rows."),
("MIN", "Min", "Gets the minimum value of all rows."),
("MAX", "Max", "Gets the maximum value of all rows."),
("NONE", "None", "Don't provide a summary row"),
("SUM", "Sum", "Sums the total value of all rows"),
("AVERAGE", "Average", "Averages the total value of all rows"),
("MIN", "Min", "Gets the minimum value of all rows"),
("MAX", "Max", "Gets the maximum value of all rows"),
]
)
@@ -64,18 +64,6 @@ class CsvProperties(PropertyGroup):
csv_ifc_file: StringProperty(default="", name="IFC File")
ifc_selector: StringProperty(default="", name="IFC Selector")
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
facet: EnumProperty(
items=[
("entity", "Class", "", "FILE_3D", 0),
("attribute", "Attribute", "", "COPY_ID", 1),
("property", "Property", "", "PROPERTIES", 2),
("material", "Material", "", "MATERIAL", 3),
("classification", "Classification", "", "OUTLINER", 4),
("location", "Location", "", "PACKAGE", 5),
("type", "Type", "", "FILE_VOLUME", 6),
("instance", "GlobalId", "", "GRIP", 7),
],
)
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
include_global_id: BoolProperty(default=True, name="Include GlobalId")
@@ -87,6 +87,7 @@ class BIM_PT_ifccsv(Panel):
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
total = len(props.csv_attributes)
for index, attribute in enumerate(props.csv_attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
@@ -99,6 +100,15 @@ class BIM_PT_ifccsv(Panel):
row.prop(attribute, "varies_value", text="")
if props.should_show_summary:
row.prop(attribute, "summary", text="")
if total > 1:
if index != 0:
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="")
op.old_index = index
op.new_index = index - 1
if index + 1 != total:
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_DOWN", text="")
op.old_index = index
op.new_index = index + 1
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
row = layout.row(align=True)
@@ -97,7 +97,7 @@ class ConnectionsData:
@classmethod
def load(cls):
cls.data = {"connections": cls.connections()}
cls.data = {"connections": cls.connections(), "is_connection_realization": cls.is_connection_realization()}
cls.is_loaded = True
@classmethod
@@ -114,7 +114,13 @@ class ConnectionsData:
else:
related_element = rel.RelatedElement
if element.is_a("IfcRelConnectsPathElements"):
realizing_elements = []
realizing_elements_connection_type = ""
if rel.is_a("IfcRelConnectsWithRealizingElements"):
realizing_elements.extend(rel.RealizingElements)
realizing_elements_connection_type = rel.ConnectionType
if rel.is_a("IfcRelConnectsPathElements"):
related_element_connection_type = rel.RelatedConnectionType
else:
related_element_connection_type = ""
@@ -125,6 +131,8 @@ class ConnectionsData:
"is_relating": True,
"Name": related_element.Name or "Unnamed",
"ConnectionType": related_element_connection_type,
"realizing_elements": realizing_elements,
"realizing_elements_connection_type": realizing_elements_connection_type,
}
)
@@ -134,7 +142,13 @@ class ConnectionsData:
else:
relating_element = rel.RelatingElement
if element.is_a("IfcRelConnectsPathElements"):
realizing_elements = []
realizing_elements_connection_type = ""
if rel.is_a("IfcRelConnectsWithRealizingElements"):
realizing_elements.extend(rel.RealizingElements)
realizing_elements_connection_type = rel.ConnectionType
if rel.is_a("IfcRelConnectsPathElements"):
relating_element_connection_type = rel.RelatingConnectionType
else:
relating_element_connection_type = ""
@@ -145,11 +159,30 @@ class ConnectionsData:
"is_relating": False,
"Name": relating_element.Name or "Unnamed",
"ConnectionType": relating_element_connection_type,
"realizing_elements": realizing_elements,
"realizing_elements_connection_type": realizing_elements_connection_type,
}
)
return results
@classmethod
def is_connection_realization(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
connections = element.IsConnectionRealization
if not connections:
return
results = []
for rel in connections:
data = {
"realizing_elements_connection_type": rel.ConnectionType,
"connected_from": rel.RelatingElement,
"connected_to": rel.RelatedElement,
}
results.append(data)
return results
class DerivedPlacementsData:
data = {}
@@ -329,7 +329,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
"style.assign_representation_styles",
self.file,
shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj),
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
)
tool.Geometry.record_object_materials(obj)
@@ -397,6 +397,8 @@ class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
def _execute(self, context):
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
self.report({"INFO"}, f"{len(parameters)} parameters found.")
class CopyRepresentation(bpy.types.Operator, Operator):
@@ -1370,8 +1372,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
continue
is_profile = True
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
if usage_type == "PROFILE":
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
elif representation_class and "IfcCircleProfileDef" in representation_class:
self.report({"INFO"}, "Can't edit Circle Profile Extrusion")
obj.select_set(False)
continue
elif (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
@@ -1418,8 +1425,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
else:
obj.select_set(False)
continue
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
if len(selected_objs) > 1 and (not context.selected_objects or len(context.selected_objects) != len(selected_objs)):
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
@@ -134,7 +134,7 @@ class BIM_PT_connections(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
if not ConnectionsData.data["connections"]:
if not ConnectionsData.data["connections"] and not ConnectionsData.data["is_connection_realization"]:
layout.label(text="No connections found")
for connection in ConnectionsData.data["connections"]:
@@ -146,6 +146,44 @@ class BIM_PT_connections(Panel):
op = row.operator("bim.remove_connection", icon="X", text="")
op.connection = connection["id"]
if connection["realizing_elements"]:
row = self.layout.row(align=True)
connection_type = connection["realizing_elements_connection_type"]
connection_type = f" ({connection_type})" if connection_type else ""
row.label(text=f"Realizing elements{connection_type}:")
for element in connection["realizing_elements"]:
row = self.layout.row(align=True)
obj = tool.Ifc.get_object(element)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = element.id()
row.label(text=obj.name)
# display connections where element is connection realization
connections = ConnectionsData.data["is_connection_realization"]
if not connections:
return
row = self.layout.row(align=True)
row.label(text="Element is connections realization:")
for connection in connections:
# NOTE: not displayed yet
connection_type = connection["realizing_elements_connection_type"]
connection_type = f" ({connection_type})" if connection_type else ""
row = self.layout.row(align=True)
connected_from = connection["connected_from"]
obj = tool.Ifc.get_object(connected_from)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_from.id()
row.label(text=obj.name)
row.label(text="", icon="FORWARD")
connected_to = connection["connected_to"]
obj = tool.Ifc.get_object(connected_to)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_to.id()
row.label(text=obj.name)
class BIM_PT_mesh(Panel):
bl_label = "Representation Utilities"
@@ -688,7 +688,13 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
)
if p not in (start_point, end_point)
]
entire_length = (first_segment_start - second_segment_end).length
def get_segments_length():
start_dir = (start_point - first_segment_start).normalized()
segments_vector = second_segment_end - first_segment_start
return segments_vector.dot(start_dir)
entire_length = get_segments_length()
# can't rely on (end_point-start_point) here because
# transition might change the segments length and therefore direction will be changed
@@ -717,28 +723,40 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
f"Failed to add transition - transition length is larger the segments and the distance between them.\n"
+ f"Transition length: {full_transition_length:.2f}m, segments length: {entire_length:.2f}m",
)
# TODO: handle the case without creating a representation in the first place?
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
return {"CANCELLED"}
# calculate bunch of points to for adjustments
middle_point = keep_only_z_axis((start_point + end_point) / 2 - start_point) + start_point
start_segment_extend_point = middle_point - segments_dir * full_transition_length / 2
end_segment_extend_point = middle_point + segments_dir * full_transition_length / 2 + profile_offset_ws
transition_dir = keep_only_z_axis(end_segment_extend_point - start_segment_extend_point).normalized()
DumbProfileJoiner().join_E(start_object, start_segment_extend_point)
DumbProfileJoiner().join_E(end_object, end_segment_extend_point)
# adjust the segments
end_object_rotation = end_object.matrix_world.to_quaternion()
end_object_z_basis = end_object_rotation.to_matrix().col[2] # z basis vector
if tool.Cad.is_x(start_object_z_basis.dot(transition_dir), 1):
start_connection = "ATEND"
else:
start_connection = "ATSTART"
if tool.Cad.is_x(end_object_z_basis.dot(transition_dir), 1):
end_connection = "ATSTART"
else:
end_connection = "ATEND"
DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection)
DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection)
# find the compatible fitting type
fitting_data = MEPGenerator().get_compatible_fitting_type(
[start_element, end_element], [start_port, end_port], "TRANSITION"
)
transition_type = fitting_data["fitting_type"] if fitting_data else None
if transition_type:
# TODO: handle the case without creating a representation in the first place?
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
start_port_match = fitting_data["start_port_match"] if fitting_data else True
# create new fitting type if nothing is compatible
if not transition_type:
mesh = bpy.data.meshes.new("Transition")
obj = bpy.data.objects.new("Transition", mesh)
@@ -762,23 +780,28 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
)
# NOTE: at this point we loose current blender objects selection
# create transition element
bpy.ops.bim.add_constr_type_instance(relating_type_id=transition_type.id())
transition_obj = bpy.context.active_object
# adjust transition segment rotation and location
# required since we'll base our `transition_obj_dir` on this
transition_obj.matrix_world = start_object.matrix_world
context.view_layer.update()
# depending on transition direction we may need to flip it or attach it's origin to end segment
# direction can be different depending on:
# - order of the current segments
# - order of the segments that were used with the same transition type before
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
direction_match = tool.Cad.are_vectors_equal(transition_dir, transition_obj_dir)
# if there are no mismatches or everything matches up we don't need to flip the transition
if start_port_match != direction_match:
transition_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X")
transition_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
# add ports and connect them
ports = tool.System.add_ports(transition_obj)
ports = tool.System.add_ports(transition_obj, offset_end_port=profile_offset_ws)
if not start_port_match:
start_port, end_port = end_port, start_port
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
@@ -70,7 +70,10 @@ class FilledOpeningGenerator:
)
if target is None:
target = bpy.context.scene.cursor.location
should_set_z_level = True
target = bpy.context.scene.cursor.location.copy()
else:
should_set_z_level = False
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
if voided_obj.data:
@@ -86,12 +89,17 @@ class FilledOpeningGenerator:
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
new_matrix = voided_obj.matrix_world.copy()
new_matrix.translation = tool.Cad.point_on_edge(target, axis)
point_on_axis = tool.Cad.point_on_edge(target, axis)
new_matrix.translation.x = point_on_axis.x
new_matrix.translation.y = point_on_axis.y
if filling.is_a("IfcDoor"):
new_matrix.translation.z = voided_obj.matrix_world.translation.z
if should_set_z_level:
if filling.is_a("IfcDoor"):
new_matrix.translation.z = voided_obj.matrix_world.translation.z
else:
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
else:
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
filling_obj.matrix_world = new_matrix
bpy.context.view_layer.update()
@@ -498,10 +506,14 @@ class AddBoolean(Operator, tool.Ifc.Operator):
bl_idname = "bim.add_boolean"
bl_label = "Add Boolean"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Applies a boolean to the selected IFC object using the other selected blender object as a void"
@classmethod
def poll(cls, context):
return len(context.selected_objects) == 2
if not len(context.selected_objects) == 2:
cls.poll_message_set("Exactly 2 objects need to be selected.")
return False
return True
def _execute(self, context):
props = context.scene.BIMModelProperties
@@ -646,6 +658,7 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
upstream_obj = None
for obj in context.selected_objects:
if (
not obj.data
@@ -673,6 +686,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
should_sync_changes_first=False,
)
bpy.data.objects.remove(obj)
tool.Blender.set_active_object(upstream_obj)
return {"FINISHED"}
@@ -272,13 +272,21 @@ class DumbProfileJoiner:
body = copy.deepcopy(axis1)
self.recreate_profile(element1, profile1, axis, body)
def join_E(self, profile1, target):
def join_E(self, profile1, target, connection=None):
"""`connection` = `ATEND` / `ATSTART` to explicitly define the reference point for the join.
For example if profile 1m long and `target` is at (0, 0, 0.1) and `connection` = `None`
it will implicitly use `connection` = `ATSTART` resulting in profile object 0.9m long and moved to (0, 0, 0.1).
But with `connection` = `ATEND` it will result in the profile object 0.1m long, locaiton unchanged.
"""
element1 = tool.Ifc.get_entity(profile1)
if not element1:
return
axis1 = self.get_profile_axis(profile1)
intersect, connection = mathutils.geometry.intersect_point_line(target, *axis1)
connection = "ATEND" if connection > 0.5 else "ATSTART"
intersect, connection_value = mathutils.geometry.intersect_point_line(target, *axis1)
if connection is None:
connection = "ATEND" if connection_value > 0.5 else "ATSTART"
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)
@@ -78,9 +78,11 @@ class WallTool(BimTool):
bl_description = "Create and edit walls"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.wall")
bl_widget = None
ifc_element_type = "IfcWallType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcWallType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class SlabTool(BimTool):
@@ -91,9 +93,11 @@ class SlabTool(BimTool):
bl_description = "Create and edit slabs"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.slab")
bl_widget = None
ifc_element_type = "IfcSlabType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcSlabType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class DoorTool(BimTool):
@@ -104,9 +108,11 @@ class DoorTool(BimTool):
bl_description = "Create and edit doors"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.door")
bl_widget = None
ifc_element_type = "IfcDoorType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcDoorType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class WindowTool(BimTool):
@@ -117,9 +123,11 @@ class WindowTool(BimTool):
bl_description = "Create and edit windows"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.window")
bl_widget = None
ifc_element_type = "IfcWindowType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcWindowType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class ColumnTool(BimTool):
@@ -130,9 +138,11 @@ class ColumnTool(BimTool):
bl_description = "Create and edit columns"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.column")
bl_widget = None
ifc_element_type = "IfcColumnType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcColumnType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class BeamTool(BimTool):
@@ -143,9 +153,11 @@ class BeamTool(BimTool):
bl_description = "Create and edit beams"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.beam")
bl_widget = None
ifc_element_type = "IfcBeamType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcBeamType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class DuctTool(BimTool):
@@ -156,9 +168,11 @@ class DuctTool(BimTool):
bl_description = "Create and edit ducks" # No, not a typo.
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.duct")
bl_widget = None
ifc_element_type = "IfcDuctSegmentType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcDuctSegmentType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class PipeTool(BimTool):
@@ -169,9 +183,11 @@ class PipeTool(BimTool):
bl_description = "Create and edit pipes"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.pipe")
bl_widget = None
ifc_element_type = "IfcPipeSegmentType"
def draw_settings(context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type="IfcPipeSegmentType")
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
def add_layout_hotkey_operator(layout, text, hotkey, description):
@@ -394,9 +410,7 @@ class BimToolUI:
else:
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
if AuthoringData.data["active_class"] in (
"IfcOpeningElement",
):
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
if len(context.selected_objects) == 2:
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
@@ -737,9 +751,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
self.props.z = self.z
def hotkey_S_L(self):
if AuthoringData.data["active_class"] in (
"IfcOpeningElement",
):
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
if len(bpy.context.selected_objects) == 2:
bpy.ops.bim.clone_opening()
@@ -761,3 +773,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.edit_openings()
else:
bpy.ops.bim.show_openings()
LIST_OF_TOOLS = [cls.bl_idname for cls in (BimTool.__subclasses__() + [BimTool])]
TOOLS_TO_CLASSES_MAP = {cls.bl_idname: cls.ifc_element_type for cls in BimTool.__subclasses__()}
@@ -50,6 +50,8 @@ class ProfileData:
@classmethod
def active_profile_users(cls):
profiles_props = bpy.context.scene.BIMProfileProperties
if profiles_props.active_profile_index >= len(profiles_props.profiles):
return 0
profile_prop = profiles_props.profiles[profiles_props.active_profile_index]
profile_ifc = tool.Ifc.get().by_id(profile_prop.ifc_definition_id)
return tool.Ifc.get().get_total_inverses(profile_ifc)
@@ -455,7 +455,13 @@ class CopyPropertyToSelection(bpy.types.Operator, Operator):
else:
is_pset = context.active_object.PsetProperties.active_pset_type == "PSET"
pset_name = context.active_object.PsetProperties.active_pset_name
prop_value = context.active_object.PsetProperties.properties.get(self.name).metadata.get_value()
prop = context.active_object.PsetProperties.properties.get(self.name)
if prop.value_type == "IfcPropertySingleValue":
prop_value = prop.metadata.get_value()
elif prop.value_type == "IfcPropertyEnumeratedValue":
value_name = prop.metadata.get_value_name()
prop_value = [e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected]
for obj in tool.Blender.get_selected_objects():
core.copy_property_to_selection(
tool.Ifc,
@@ -71,6 +71,9 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
grid = layout.column_flow(columns=3)
for e in prop.enumerated_value.enumerated_values:
grid.prop(e, "is_selected", text=str(e[value_name]))
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.name = prop.metadata.name
def get_active_pset_obj_name(context, obj_type):
@@ -20,36 +20,41 @@ import bpy
from . import ui, prop, operator
classes = (
operator.DisableResourceEditingUI,
operator.DisableEditingResource,
operator.EnableEditingResource,
operator.LoadResources,
operator.AddResource,
operator.AddResourceQuantity,
operator.EditResource,
operator.RemoveResource,
operator.RemoveResourceQuantity,
operator.LoadResourceProperties,
operator.ExpandResource,
operator.ContractResource,
operator.AddProductivityData,
operator.AssignResource,
operator.UnassignResource,
operator.EnableEditingResourceTime,
operator.EnableEditingResourceQuantity,
operator.EnableEditingResourceBaseQuantity,
operator.EnableEditingResourceCosts,
operator.EnableEditingResourceCostValueFormula,
operator.EnableEditingResourceCostValue,
operator.EditResourceTime,
operator.EditResourceQuantity,
operator.CalculateResourceWork,
operator.ConstrainResourceWork,
operator.ContractResource,
operator.DisableEditingResource,
operator.DisableEditingResourceCostValue,
operator.DisableEditingResourceQuantity,
operator.DisableEditingResourceTime,
operator.DisableResourceEditingUI,
operator.EditProductivityData,
operator.EditResource,
operator.EditResourceCostValue,
operator.EditResourceCostValueFormula,
operator.DisableEditingResourceTime,
operator.DisableEditingResourceQuantity,
operator.DisableEditingResourceCostValue,
operator.CalculateResourceWork,
operator.EditResourceQuantity,
operator.EditResourceTime,
operator.EnableEditingResource,
operator.EnableEditingResourceBaseQuantity,
operator.EnableEditingResourceCosts,
operator.EnableEditingResourceCostValue,
operator.CalculateResourceUsage,
operator.EnableEditingResourceCostValueFormula,
operator.EnableEditingResourceQuantity,
operator.EnableEditingResourceTime,
operator.ExpandResource,
operator.GoToResource,
operator.ImportResources,
operator.EditProductivityData,
operator.LoadResourceProperties,
operator.LoadResources,
operator.RemoveResource,
operator.RemoveResourceQuantity,
operator.RemoveUsageConstraint,
operator.UnassignResource,
prop.Resource,
prop.BIMResourceProperties,
prop.BIMResourceTreeProperties,
@@ -56,21 +56,75 @@ class ResourceData:
"type": resource.is_a(),
"BaseQuantity": base_quantity,
}
results[resource.id()]["Benchmarks"] = cls.get_resource_benchmarks(resource)
if resource.is_a() in ["IfcLaborResource", "IfcConstructionEquipmentResource"]:
results[resource.id()]["Productivity"] = {}
results[resource.id()]["InheritedProductivity"] = {}
productivity = cls.get_productivity(resource)
if productivity:
results[resource.id()]["Productivity"] = {
"id": productivity.get("id"),
"QuantityProduced": ifcopenshell.util.resource.get_quantity_produced(productivity),
"TimeConsumed": ifcopenshell.util.resource.get_unit_consumed(productivity),
"QuantityProducedName": ifcopenshell.util.resource.get_quantity_produced_name(productivity),
}
inherited_productivity = cls.get_parent_productivity(resource)
if inherited_productivity:
results[resource.id()]["InheritedProductivity"] = {
"QuantityProduced": ifcopenshell.util.resource.get_quantity_produced(inherited_productivity),
"TimeConsumed": ifcopenshell.util.resource.get_unit_consumed(inherited_productivity),
"QuantityProducedName": ifcopenshell.util.resource.get_quantity_produced_name(
inherited_productivity
),
}
if resource.Usage:
results[resource.id()]["ScheduleWork"] = (
ifcopenshell.util.date.readable_ifc_duration(resource.Usage.ScheduleWork)
if resource.Usage.ScheduleWork
else None
)
results[resource.id()]["ScheduleUsage"] = (
resource.Usage.ScheduleUsage if resource.Usage.ScheduleUsage else None
)
if resource.IsNestedBy:
results[resource.id()]["DerivedScheduleWork"] = cls.sum_person_hours(resource)
return results
@classmethod
def sum_person_hours(cls, resource):
sum = 0
nested_resources = ifcopenshell.util.resource.get_nested_resources(resource)
for nested_resource in nested_resources or []:
if not nested_resource.Usage or not nested_resource.Usage.ScheduleWork:
continue
duration = ifcopenshell.util.date.ifc2datetime(nested_resource.Usage.ScheduleWork)
sum += duration.total_seconds() / 3600
return round(float(sum), 2) if sum else 0
@classmethod
def get_resource_benchmarks(cls, resource):
constraints = []
for constraint in ifcopenshell.util.constraint.get_constraints(resource) or []:
metrics = []
for metric in ifcopenshell.util.constraint.get_metrics(constraint) or []:
metrics.append(
{
"reference": ifcopenshell.util.constraint.get_metric_reference(metric),
"Benchmark": metric.Benchmark,
"ConstraintGrade": metric.ConstraintGrade,
}
)
constraints.append({"ObjectiveQualifier": constraint.ObjectiveQualifier, "metrics": metrics})
return constraints
@classmethod
def get_productivity(cls, resource):
return ifcopenshell.util.resource.get_productivity(resource, should_inherit=False)
@classmethod
def get_parent_productivity(cls, resource):
return ifcopenshell.util.resource.get_parent_productivity(resource)
@classmethod
def cost_values(cls):
results = []
@@ -18,6 +18,7 @@
import bpy
from bpy_extras.io_utils import ImportHelper
from blenderbim.bim.module.resource.ui import draw_productivity_ui
import blenderbim.core.resource as core
import blenderbim.tool as tool
@@ -198,6 +199,13 @@ class CalculateResourceWork(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
active_resource = tool.Resource.get_highlighted_resource()
if active_resource:
if tool.Resource.get_productivity(active_resource, should_inherit=True):
return True
def _execute(self, context):
core.calculate_resource_work(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
@@ -348,6 +356,16 @@ class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
core.import_resources(tool.Resource, file_path=self.filepath)
class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_productivity_data"
bl_description = "Apply"
bl_label = "Add Productivity"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
tool.Ifc.run("pset.add_pset", product=tool.Resource.get_highlighted_resource(), name="EPset_Productivity")
class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_productivity_data"
bl_description = "Apply"
@@ -356,3 +374,67 @@ class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.edit_productivity_pset(tool.Ifc, tool.Resource)
def draw(self, context):
draw_productivity_ui(self, context)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=600)
class ConstrainResourceWork(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_usage_constraint"
bl_label = "Constrain Resource Work"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
attribute: bpy.props.StringProperty()
def _execute(self, context):
core.add_usage_constraint(
tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute
)
class RemoveUsageConstraint(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_usage_constraint"
bl_label = "Remove Usage Constraint"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
attribute: bpy.props.StringProperty()
def _execute(self, context):
core.remove_usage_constraint(
tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute
)
class GoToResource(bpy.types.Operator):
bl_idname = "bim.go_to_resource"
bl_label = "Go To Resource"
bl_description = "Selects the resource in the Resource Panel"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
core.go_to_resource(tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
return {"FINISHED"}
class CalculateResourceUsage(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.calculate_resource_usage"
bl_label = "Calculate Resource Usage"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
active_resource = tool.Resource.get_highlighted_resource()
if active_resource:
if active_resource.Usage and active_resource.Usage.ScheduleWork:
task = tool.Resource.get_task_assignments(active_resource)
if task and tool.Sequence.has_duration(task):
return True
return False
def _execute(self, context):
core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(tool.Resource.get_highlighted_resource()))
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2021, 2022, 2023 Dion Moult, Yassine Oualid <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -22,6 +22,8 @@ import ifcopenshell.util.resource
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
import blenderbim.bim.module.pset.data
import blenderbim.bim.module.resource.data
import blenderbim.bim.module.sequence.data
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -56,6 +58,8 @@ def updateResourceName(self, context):
if props.active_resource_id == self.ifc_definition_id:
attribute = props.resource_attributes.get("Name")
attribute.string_value = self.name
blenderbim.bim.module.resource.data.refresh()
tool.Sequence.refresh_task_resources()
def get_quantity_types(self, context):
@@ -72,7 +76,7 @@ def get_quantity_types(self, context):
def update_active_resource_index(self, context):
blenderbim.bim.module.pset.data.refresh()
if self.should_show_productivity:
if self.should_show_resource_tools:
tool.Resource.load_productivity_data()
@@ -80,18 +84,20 @@ def updateResourceUsage(self, context):
props = context.scene.BIMResourceProperties
if not props.is_resource_update_enabled:
return
if self.schedule_usage == "":
if not self.schedule_usage:
return
resource = tool.Ifc.get().by_id(self.ifc_definition_id)
if not resource.Usage:
tool.Ifc.run(
"resource.add_resource_time",
resource=resource,
)
resource.Usage.ScheduleUsage = self.schedule_usage
blenderbim.bim.module.pset.data.refresh()
if resource.Usage and resource.Usage.ScheduleUsage == self.schedule_usage:
return
tool.Resource.run_edit_resource_time(resource, attributes={
"ScheduleUsage": self.schedule_usage
})
tool.Sequence.load_task_properties()
tool.Resource.load_resource_properties()
tool.Sequence.refresh_task_resources()
blenderbim.bim.module.resource.data.refresh()
blenderbim.bim.module.sequence.data.refresh()
blenderbim.bim.module.pset.data.refresh()
class ISODuration(PropertyGroup):
@@ -144,7 +150,7 @@ class BIMResourceProperties(PropertyGroup):
quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types")
is_editing_quantity: BoolProperty(name="Is Editing Quantity")
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
should_show_productivity: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
should_show_resource_tools: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
class BIMResourceProductivity(PropertyGroup):
@@ -28,7 +28,8 @@ class BIM_PT_resources(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_resources"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -64,58 +65,124 @@ class BIM_PT_resources(Panel):
self.props,
"active_resource_index",
)
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.props, "should_show_productivity", icon="RECOVER_LAST")
if self.props.should_show_productivity:
self.draw_productivity_ui(context)
if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES":
self.draw_editable_resource_attributes_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY":
self.draw_editable_resource_quantity_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
self.draw_editable_resource_costs_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
self.draw_editable_resource_time_attributes_ui()
if self.props.active_resource_id:
if self.props.editing_resource_type == "ATTRIBUTES":
self.draw_editable_resource_attributes_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY":
self.draw_editable_resource_quantity_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
self.draw_editable_resource_costs_ui()
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
self.draw_editable_resource_time_attributes_ui()
self.draw_productivity_ui(context)
def draw_productivity_ui(self, context):
total_resources = len(self.tprops.resources)
if not total_resources or self.props.active_resource_index >= total_resources:
return
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
resource = ResourceData.data["resources"][ifc_definition_id]
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
row = self.layout.row(align=True)
row.label(text="Resource type cannot have productivity data", icon="ERROR")
return
self.productivity_props = context.scene.BIMResourceProductivity
if resource["Productivity"]:
produtivitiy_rate_message = "Current Rate: {}/{}".format(
resource["Productivity"]["QuantityProduced"], resource["Productivity"]["TimeConsumed"]
)
row = self.layout.row()
row.alignment = "LEFT"
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
else:
row = self.layout.row(align=True)
row.alignment = "LEFT"
produtivitiy_rate_message = "No productivity data found"
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.productivity_props, "quantity_produced", text="Quantity Produced")
row.prop(self.productivity_props, "quantity_produced_name", text="Quantity Name")
row = self.layout.row()
row.alignment = "RIGHT"
self.draw_duration_property(self.productivity_props.quantity_consumed, row)
row = self.layout.row()
row.alignment = "RIGHT"
row.operator("bim.edit_productivity_data", text="Apply", icon="CHECKMARK")
row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST")
if self.props.should_show_resource_tools:
total_resources = len(self.tprops.resources)
if not total_resources or self.props.active_resource_index >= total_resources:
return
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
resource = ResourceData.data["resources"][ifc_definition_id]
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
row = self.layout.row(align=True)
row.label(text="Resource type cannot have productivity data", icon="ERROR")
else:
is_usage_locked = False
is_work_locked = False
for constraint in resource["Benchmarks"] or []:
for metric in constraint["metrics"] or []:
if (
metric["ConstraintGrade"] == "HARD"
and metric["reference"]
and metric["reference"] == "Usage.ScheduleUsage"
):
is_usage_locked = True
elif (
metric["ConstraintGrade"] == "HARD"
and metric["reference"]
and metric["reference"] == "Usage.ScheduleWork"
):
is_work_locked = True
grid = self.layout.grid_flow(columns=3, even_columns=False, even_rows=False, align=False)
col1 = grid.column(align=True)
col2 = grid.column(align=False)
col3 = grid.column(align=True)
col1.ui_units_x = 1
col2.ui_units_x = 1
col3.ui_units_x = 2
row1_col1 = col1.row()
row1_col1.label(text="Schedule Work")
row1col2 = col2.row()
schedule_work = resource.get("ScheduleWork", None)
derived_schedule_work = resource.get("DerivedScheduleWork", None)
row1col2.label(
text="{}".format(schedule_work) if schedule_work else "{} h*".format(derived_schedule_work),
icon="TIME",
)
row1col3 = col3.row()
row1col3.operator("bim.calculate_resource_work", text="", icon="TEMP").resource = ifc_definition_id
op = row1col3.operator(
"bim.add_usage_constraint" if not is_work_locked else "bim.remove_usage_constraint",
text="",
icon="LOCKED" if is_work_locked else "UNLOCKED",
)
op.resource = ifc_definition_id
op.attribute = "Usage.ScheduleWork"
row2_col1 = col1.row()
row2_col1.label(text="Schedule Usage")
row2col2 = col2.row()
row2col2.prop(self.tprops.resources[self.props.active_resource_index], "schedule_usage", text="")
row2col3 = col3.row()
row2col3.operator("bim.calculate_resource_usage", text="", icon="TEMP")
op = row2col3.operator(
"bim.add_usage_constraint" if not is_usage_locked else "bim.remove_usage_constraint",
text="",
icon="LOCKED" if is_usage_locked else "UNLOCKED",
)
op.resource = ifc_definition_id
op.attribute = "Usage.ScheduleUsage"
productivity = resource["Productivity"]
parent_productivity = resource["InheritedProductivity"]
row = self.layout.row()
if productivity:
produtivitiy_rate_message = "Current Productivity Rate: {} {} / {}".format(
productivity["QuantityProduced"],
productivity["QuantityProducedName"],
productivity["TimeConsumed"],
)
row.alignment = "LEFT"
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
row.operator("bim.edit_productivity_data", text="", icon="GREASEPENCIL")
op = row.operator("bim.remove_pset", text="", icon="X")
op.pset_id = productivity["id"]
op.obj_type = "Resource"
op.obj = ""
elif parent_productivity:
produtivitiy_rate_message = "Inherited Productivity Rate: {} {} / {}*".format(
parent_productivity["QuantityProduced"],
parent_productivity["QuantityProducedName"],
parent_productivity["TimeConsumed"],
)
row.alignment = "LEFT"
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
row.operator("bim.add_productivity_data", text="", icon="ADD")
else:
row = self.layout.row(align=True)
row.alignment = "LEFT"
produtivitiy_rate_message = "No productivity data found"
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
row.operator("bim.add_productivity_data", text="", icon="ADD")
def draw_resource_operators(self):
row = self.layout.row(align=True)
@@ -153,9 +220,6 @@ class BIM_PT_resources(Panel):
if not self.props.active_resource_id:
if resource["type"] in ["IfcLaborResource", "IfcConstructionEquipmentResource"]:
if resource["Productivity"]:
op = row.operator("bim.calculate_resource_work", text="", icon="TEMP")
op.resource = ifc_definition_id
row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = ifc_definition_id
op = row.operator("bim.enable_editing_resource_base_quantity", text="", icon="PROPERTIES")
op.resource = ifc_definition_id
@@ -163,6 +227,12 @@ class BIM_PT_resources(Panel):
op.resource = ifc_definition_id
row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
else:
if self.props.editing_resource_type == "ATTRIBUTES":
row.operator("bim.edit_resource", text="", icon="CHECKMARK")
elif self.props.editing_resource_type == "USAGE":
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
def draw_editable_resource_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.resource_attributes, self.layout)
@@ -250,7 +320,6 @@ class BIM_PT_resources(Panel):
def draw_duration_property(self, duration_props, layout):
for duration_prop in duration_props:
if duration_prop.name == "BaseQuantityConsumed":
layout.label(text=duration_prop.name)
layout.prop(duration_prop, "years", text="Y")
layout.prop(duration_prop, "months", text="M")
layout.prop(duration_prop, "days", text="D")
@@ -287,7 +356,7 @@ class BIM_UL_resources(UIList):
else:
row.label(text="", icon="DOT")
row.prop(item, "name", emboss=False, text="", icon=icon_map[resource["type"]])
row.prop(item, "schedule_usage", text="", emboss=False)
row.prop(item, "schedule_usage", text="", emboss=False) if item.schedule_usage else None
if context.active_object and not props.active_resource_id:
row = layout.row(align=True)
if item.ifc_definition_id in ResourceData.data["active_resource_ids"]:
@@ -303,3 +372,29 @@ class BIM_UL_resources(UIList):
elif props.editing_resource_type == "USAGE":
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
def draw_productivity_ui(self, context):
def draw_duration_property(duration_props, layout):
for duration_prop in duration_props:
if duration_prop.name == "BaseQuantityConsumed":
layout.prop(duration_prop, "years", text="Y")
layout.prop(duration_prop, "months", text="M")
layout.prop(duration_prop, "days", text="D")
layout.prop(duration_prop, "hours", text="H")
layout.prop(duration_prop, "minutes", text="Min")
layout.prop(duration_prop, "seconds", text="S")
productivity_props = context.scene.BIMResourceProductivity
grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False)
col1 = grid.column(align=False)
col2 = grid.column(align=False)
row1_col1 = col1.row()
row1_col1.label(text="Quantity")
row1_col2 = col2.row()
row1_col2.prop(productivity_props, "quantity_produced", text="")
row1_col2.prop(productivity_props, "quantity_produced_name", text="")
row2_col1 = col1.row()
row2_col1.label(text="Time")
row2_col2 = col2.row()
draw_duration_property(productivity_props.quantity_consumed, row2_col2)
@@ -82,7 +82,7 @@ classes = (
operator.ExportP6,
operator.GenerateGanttChart,
operator.GuessDateRange,
operator.HighlightTask,
operator.GoToTask,
operator.ImportCSV,
operator.ImportMSP,
operator.ImportP6,
@@ -118,12 +118,18 @@ classes = (
operator.UnassignWorkSchedule,
operator.VisualiseWorkScheduleDate,
operator.VisualiseWorkScheduleDateRange,
operator.EnableStatusFilters,
operator.DisableStatusFilters,
operator.ActivateStatusFilters,
operator.SelectStatusFilter,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.Task,
prop.TaskResource,
prop.TaskProduct,
prop.ISODuration,
prop.IFCStatus,
prop.BIMStatusProperties,
prop.BIMWorkScheduleProperties,
prop.BIMTaskTreeProperties,
prop.BIMTaskTypeColor,
@@ -133,6 +139,7 @@ classes = (
prop.BIMWorkCalendarProperties,
prop.DatePickerProperties,
prop.BIMDateTextProperties,
ui.BIM_PT_status,
ui.BIM_PT_work_plans,
ui.BIM_PT_work_schedules,
ui.BIM_PT_work_calendars,
@@ -165,6 +172,7 @@ def menu_func_import(self, context):
def register():
bpy.types.Scene.BIMStatusProperties = bpy.props.PointerProperty(type=prop.BIMStatusProperties)
bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties)
bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties)
bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties)
@@ -177,6 +185,7 @@ def register():
def unregister():
del bpy.types.Scene.BIMStatusProperties
del bpy.types.Scene.BIMWorkPlanProperties
del bpy.types.Scene.BIMWorkScheduleProperties
del bpy.types.Scene.BIMTaskTreeProperties
@@ -27,11 +27,98 @@ import webbrowser
import blenderbim.core.sequence as core
import blenderbim.tool as tool
import blenderbim.bim.module.sequence.helper as helper
import ifcopenshell.util.sequence
import ifcopenshell.util.selector
from datetime import datetime
from dateutil import parser, relativedelta
from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
import ifcopenshell.util.sequence
class EnableStatusFilters(bpy.types.Operator):
bl_idname = "bim.enable_status_filters"
bl_label = "Enable Status Filters"
def execute(self, context):
props = context.scene.BIMStatusProperties
props.is_enabled = True
props.statuses.clear()
statuses = set()
for element in tool.Ifc.get().by_type("IfcPropertyEnumeratedValue"):
if element.Name == "Status":
pset = element.PartOfPset[0]
if pset.Name.startswith("Pset_") and pset.Name.endswith("Common"):
statuses.update(element.EnumerationValues)
elif pset.Name == "EPset_Status": # Our secret sauce
statuses.update(element.EnumerationValues)
elif element.Name == "UserDefinedStatus":
statuses.add(element.NominalValue)
statuses = ["No Status"] + sorted([s.wrappedValue for s in statuses])
for status in statuses:
new = props.statuses.add()
new.name = status
return {"FINISHED"}
class DisableStatusFilters(bpy.types.Operator):
bl_idname = "bim.disable_status_filters"
bl_label = "Disable Status Filters"
def execute(self, context):
props = context.scene.BIMStatusProperties
props.is_enabled = False
return {"FINISHED"}
class ActivateStatusFilters(bpy.types.Operator):
bl_idname = "bim.activate_status_filters"
bl_label = "Activate Status Filters"
def execute(self, context):
props = context.scene.BIMStatusProperties
query = []
visible_statuses = {s.name for s in props.statuses if s.is_visible}
for name in visible_statuses:
if name == "No Status":
q = f"IfcProduct, /Pset_.*Common/.Status=NULL, EPset_Status.Status=NULL"
else:
q = f"IfcProduct, /Pset_.*Common/.Status={name} + IfcProduct, EPset_Status.Status={name}"
query.append(q)
query = " + ".join(query)
if not query:
return {"FINISHED"}
visible_elements = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
for obj in bpy.context.view_layer.objects:
element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcProduct"):
continue
obj.hide_set(element not in visible_elements)
return {"FINISHED"}
class SelectStatusFilter(bpy.types.Operator):
bl_idname = "bim.select_status_filter"
bl_label = "Select Status Filter"
name: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMStatusProperties
query = f"IfcProduct, /Pset_.*Common/.Status={self.name} + IfcProduct, EPset_Status.Status={self.name}"
if self.name == "No Status":
query = f"IfcProduct, /Pset_.*Common/.Status=NULL, EPset_Status.Status=NULL"
for element in ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query):
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
return {"FINISHED"}
class AddWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
@@ -1323,14 +1410,14 @@ class LoadProductTasks(bpy.types.Operator):
return {"FINISHED"}
class HighlightTask(bpy.types.Operator):
bl_idname = "bim.highlight_task"
class GoToTask(bpy.types.Operator):
bl_idname = "bim.go_to_task"
bl_label = "Highlight Task"
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
r = core.highlight_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task))
r = core.go_to_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task))
if isinstance(r, str):
self.report({"WARNING"}, r)
return {"FINISHED"}
@@ -24,7 +24,8 @@ from ifcopenshell.util.doc import get_predefined_type_doc
import blenderbim.tool as tool
import blenderbim.core.sequence as core
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.sequence.data import SequenceData, AnimationColorSchemeData
from blenderbim.bim.module.sequence.data import SequenceData, AnimationColorSchemeData, refresh as refresh_sequence_data
import blenderbim.bim.module.resource.data
import blenderbim.bim.module.pset.data
from blenderbim.bim.prop import StrProperty, Attribute
from dateutil import parser
@@ -224,7 +225,9 @@ def updateTaskDuration(self, context):
task_time = tool.Ifc.run("sequence.add_task_time", task=task)
tool.Ifc.run("sequence.edit_task_time", task_time=task_time, attributes={"ScheduleDuration": duration})
SequenceData.load()
blenderbim.core.sequence.load_task_properties(tool.Sequence)
bpy.ops.bim.load_task_properties()
tool.Sequence.load_resources()
def get_schedule_predefined_types(self, context):
@@ -325,6 +328,25 @@ def get_saved_color_schemes(self, context):
return AnimationColorSchemeData.data["saved_color_schemes"]
def updateAssignedResourceName(self, context):
pass
def updateAssignedResourceUsage(self, context):
if not self.schedule_usage:
return
resource = tool.Ifc.get().by_id(self.ifc_definition_id)
if resource.Usage and resource.Usage.ScheduleUsage == self.schedule_usage:
return
tool.Resource.run_edit_resource_time(resource, attributes={
"ScheduleUsage": self.schedule_usage
})
tool.Sequence.load_task_properties()
tool.Resource.load_resource_properties()
tool.Sequence.refresh_task_resources()
blenderbim.bim.module.resource.data.refresh()
blenderbim.bim.module.sequence.data.refresh()
blenderbim.bim.module.pset.data.refresh()
class Task(PropertyGroup):
name: StringProperty(name="Name", update=updateTaskName)
identification: StringProperty(name="Identification", update=updateTaskIdentification)
@@ -352,9 +374,9 @@ class WorkPlan(PropertyGroup):
class TaskResource(PropertyGroup):
name: StringProperty(name="Name")
name: StringProperty(name="Name", update=updateAssignedResourceName)
ifc_definition_id: IntProperty(name="IFC Definition ID")
schedule_usage: FloatProperty(name="Schedule Usage")
schedule_usage: FloatProperty(name="Schedule Usage", update=updateAssignedResourceUsage)
class TaskProduct(PropertyGroup):
@@ -381,6 +403,16 @@ class ISODuration(PropertyGroup):
seconds: IntProperty(name="Seconds", default=0)
class IFCStatus(PropertyGroup):
name: StringProperty(name="Name")
is_visible: BoolProperty(name="Is Visible", default=True)
class BIMStatusProperties(PropertyGroup):
is_enabled: BoolProperty(name="Is Enabled")
statuses: CollectionProperty(name="Statuses", type=IFCStatus)
class BIMWorkScheduleProperties(PropertyGroup):
work_schedule_predefined_types: EnumProperty(
items=get_schedule_predefined_types, name="Predefined Type", default=None
@@ -30,6 +30,38 @@ from blenderbim.bim.module.sequence.data import (
)
class BIM_PT_status(Panel):
bl_label = "Status"
bl_idname = "BIM_PT_status"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_status"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
self.props = context.scene.BIMStatusProperties
if not self.props.is_enabled:
row = self.layout.row()
row.operator("bim.enable_status_filters", icon="GREASEPENCIL")
return
row = self.layout.row(align=True)
row.operator("bim.activate_status_filters", icon="TIME")
row.operator("bim.disable_status_filters", icon="CANCEL", text="")
for status in self.props.statuses:
row = self.layout.row()
row.label(text=status.name)
row.prop(status, "is_visible", text="", emboss=False, icon="HIDE_OFF" if status.is_visible else "HIDE_ON")
row.operator("bim.select_status_filter", icon="RESTRICT_SELECT_OFF", text="").name = status.name
class BIM_PT_work_plans(Panel):
bl_label = "Work Plans"
bl_idname = "BIM_PT_work_plans"
@@ -37,7 +69,7 @@ class BIM_PT_work_plans(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_sequence"
@classmethod
def poll(cls, context):
@@ -107,7 +139,7 @@ class BIM_PT_work_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_sequence"
@classmethod
def poll(cls, context):
@@ -319,6 +351,7 @@ class BIM_PT_work_schedules(Panel):
def draw_editable_sequence_ui(self, sequence, process_type):
task = SequenceData.data["tasks"][sequence[process_type]]
row = self.layout.row(align=True)
row.operator("bim.go_to_task", text="", icon="RESTRICT_SELECT_OFF").task = task["id"]
row.label(text=task["Identification"] or "XXX")
row.label(text=task["Name"] or "Unnamed")
row.label(text=sequence["SequenceType"] or "N/A")
@@ -550,7 +583,7 @@ class BIM_PT_animation_Color_Scheme(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_sequence"
@classmethod
def poll(cls, context):
@@ -733,9 +766,9 @@ class BIM_UL_task_resources(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.operator("bim.go_to_resource", text="", icon="STYLUS_PRESSURE").resource = item.ifc_definition_id
row.prop(item, "name", emboss=False, text="")
row.label(text=str(item.schedule_usage))
row.prop(item, "schedule_usage", emboss=False, text="")
class BIM_UL_animation_colors(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -758,7 +791,7 @@ class BIM_UL_product_input_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE")
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
op.task = item.ifc_definition_id
row.split(factor=0.8)
row.label(text=item.name)
@@ -768,7 +801,7 @@ class BIM_UL_product_output_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE")
op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE")
op.task = item.ifc_definition_id
row.split(factor=0.8)
row.label(text=item.name)
@@ -888,7 +921,7 @@ class BIM_PT_work_calendars(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_4D5D"
bl_parent_id = "BIM_PT_tab_sequence"
@classmethod
def poll(cls, context):
@@ -115,8 +115,17 @@ class BIM_PT_booleans(Panel):
row.operator("bim.add_boolean", text="Apply Boolean", icon="ADD")
show_boolean_button = row.row(align=True)
show_boolean_button.operator("bim.show_booleans", text="", icon="HIDE_OFF")
show_boolean_button.enabled = BooleansData.data['total_booleans'] > 0
show_boolean_button.enabled = BooleansData.data["total_booleans"] > 0
row.operator("bim.hide_booleans", text="", icon="HIDE_ON")
elif context.active_object.data.BIMMeshProperties.ifc_boolean_id:
upsteam_obj = context.active_object.data.BIMMeshProperties.obj
upstream_obj_ifc_id = upsteam_obj.BIMObjectProperties.ifc_definition_id
row = layout.row(align=True)
row.label(text="Used as a boolean operand with:")
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = upstream_obj_ifc_id
row.label(text=upsteam_obj.name)
row = layout.row()
row.operator("bim.remove_booleans", text="Remove Boolean", icon="X")
+50 -18
View File
@@ -298,7 +298,7 @@ class BIM_PT_tabs(Panel):
self.draw_tab_entry(row, "NETWORK_DRIVE", "SERVICES", is_ifc_project, aprops.tab == "SERVICES")
self.draw_tab_entry(row, "EDITMODE_HLT", "STRUCTURE", is_ifc_project, aprops.tab == "STRUCTURE")
self.draw_tab_entry(row, "NLA", "SCHEDULING", is_ifc_project, aprops.tab == "SCHEDULING")
self.draw_tab_entry(row, "PACKAGE", "FM", is_ifc_project, aprops.tab == "FM")
self.draw_tab_entry(row, "PACKAGE", "FM", True, aprops.tab == "FM")
self.draw_tab_entry(row, "COMMUNITY", "QUALITY", True, aprops.tab == "QUALITY")
self.draw_tab_entry(row, "BLENDER", "BLENDER", True, aprops.tab == "BLENDER")
row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT")
@@ -408,12 +408,53 @@ class BIM_PT_geometry(Panel):
pass
class BIM_PT_tab_4D5D(Panel):
bl_label = "Costing and Scheduling"
class BIM_PT_tab_status(Panel):
bl_label = "Status"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_resources(Panel):
bl_label = "Resources"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_cost(Panel):
bl_label = "Cost"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_sequence(Panel):
bl_label = "Construction Scheduling"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -464,6 +505,7 @@ class BIM_PT_tab_quality_control(Panel):
def draw(self, context):
pass
class BIM_PT_tab_clash_detection(Panel):
bl_label = "Clash Detection"
bl_space_type = "PROPERTIES"
@@ -477,6 +519,7 @@ class BIM_PT_tab_clash_detection(Panel):
def draw(self, context):
pass
class BIM_PT_tab_sandbox(Panel):
bl_label = "Sandbox"
bl_space_type = "PROPERTIES"
@@ -626,7 +669,7 @@ class BIM_PT_tab_handover(Panel):
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
return tool.Blender.is_tab(context, "FM")
def draw(self, context):
pass
@@ -641,7 +684,7 @@ class BIM_PT_tab_operations(Panel):
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
return tool.Blender.is_tab(context, "FM")
def draw(self, context):
pass
@@ -658,24 +701,13 @@ class UIData:
@classmethod
def version(cls):
return ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
return tool.Blender.get_blenderbim_version()
def draw_statusbar(self, context):
if not UIData.is_loaded:
UIData.load()
text = f"BlenderBIM Add-on v{UIData.data['version']}"
if blenderbim.bim.last_commit_hash != "8888888":
text += f"-{blenderbim.bim.last_commit_hash[:7]}"
self.layout.label(text=text)
+24 -33
View File
@@ -25,6 +25,14 @@ def load_brick_project(brick, filepath=None, brick_root=None):
brick.set_active_brick_class(brick_root, split_screen=True)
def new_brick_file(brick, brick_root=None):
brick.new_brick_file()
brick.import_brick_classes(brick_root)
brick.import_brick_classes(brick_root, split_screen=True)
brick.set_active_brick_class(brick_root)
brick.set_active_brick_class(brick_root, split_screen=True)
def view_brick_class(brick, brick_class=None, split_screen=False):
brick.add_brick_breadcrumb(split_screen=split_screen)
brick.clear_brick_browser(split_screen=split_screen)
@@ -35,7 +43,7 @@ def view_brick_class(brick, brick_class=None, split_screen=False):
def view_brick_item(brick, item=None, split_screen=False):
brick_class = brick.get_item_class(item)
view_brick_class(brick, brick_class=brick_class, split_screen=split_screen)
brick.run_view_brick_class(brick_class=brick_class, split_screen=split_screen)
brick.select_browser_item(item, split_screen=split_screen)
@@ -79,18 +87,17 @@ def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, librar
if library:
brick.run_assign_brick_reference(element=element, library=library, brick_uri=brick_uri)
else:
brick_uri = brick.add_brick(namespace, brick_class, label)
brick.add_brick(namespace, brick_class, label)
brick.run_refresh_brick_viewer()
brick.run_refresh_brick_viewer(split_screen=True)
def add_brick_relation(brick, brick_uri=None, predicate=None, object=None):
brick.add_relation(brick_uri, predicate, object)
brick.run_refresh_brick_viewer()
brick.run_refresh_brick_viewer(split_screen=True)
def convert_ifc_to_brick(brick, namespace=None, library=None):
# convert spaces to brick
spaces = brick.get_convertable_brick_spaces()
space_uris = {}
for space in spaces:
@@ -98,57 +105,43 @@ def convert_ifc_to_brick(brick, namespace=None, library=None):
space_uris[space] = brick_uri
if library:
brick.run_assign_brick_reference(element=space, library=library, brick_uri=brick_uri)
for space in spaces:
parent = brick.get_parent_space(space)
if parent:
brick.add_relation(space_uris[parent], "hasPart", space_uris[space])
brick.add_relation(space_uris[parent], "https://brickschema.org/schema/Brick#hasPart", space_uris[space])
# convert systems to brick
systems = brick.get_convertable_brick_systems()
system_uris = {}
for system in systems:
brick_uri = brick.add_brick_from_element(system, namespace, brick.get_brick_class(system))
space_uris[space] = brick_uri
system_uris[system] = brick_uri
if library:
brick.run_assign_brick_reference(element=system, library=library, brick_uri=brick_uri)
# convert services to brick
distribution_elements = brick.get_convertable_brick_elements()
equipment_uris = {}
for element in distribution_elements:
brick_uri = brick.add_brick_from_element(element, namespace, brick.get_brick_class(element))
equipment_uris[element] = brick_uri
space = brick.get_element_container(element)
brick.add_relation(brick_uri, "hasLocation", space_uris[space])
brick.add_relation(brick_uri, "https://brickschema.org/schema/Brick#hasLocation", space_uris[space])
systems = brick.get_element_systems(element)
for system in systems:
brick.add_relation(system_uris[system], "hasPart", brick_uri)
brick.add_relation(system_uris[system], "https://brickschema.org/schema/Brick#hasPart", brick_uri)
if library:
brick.run_assign_brick_reference(element=element, library=library, brick_uri=brick_uri)
distribution_elements = brick.get_convertable_brick_elements()
for element in distribution_elements:
feeds = brick.get_element_feeds(element)
for downstream_equipment in feeds:
brick.add_relation(equipment_uris[element], "feeds", equipment_uris[downstream_equipment])
brick.add_relation(equipment_uris[element], "https://brickschema.org/schema/Brick#feeds", equipment_uris[downstream_equipment])
brick.run_refresh_brick_viewer()
brick.run_refresh_brick_viewer(split_screen=True)
def new_brick_file(brick, brick_root=None):
brick.new_brick_file()
brick.import_brick_classes(brick_root)
brick.import_brick_classes(brick_root, split_screen=True)
brick.set_active_brick_class(brick_root)
brick.set_active_brick_class(brick_root, split_screen=True)
def refresh_brick_viewer(brick, split_screen=False):
if split_screen:
brick.run_view_brick_class(brick_class=brick.get_active_brick_class(split_screen=split_screen), split_screen=split_screen)
else:
brick.run_view_brick_class(brick_class=brick.get_active_brick_class(), split_screen=split_screen)
brick.pop_brick_breadcrumb(split_screen=split_screen)
def refresh_brick_viewer(brick):
brick.run_view_brick_class(brick_class=brick.get_active_brick_class())
brick.pop_brick_breadcrumb()
brick.run_view_brick_class(brick_class=brick.get_active_brick_class(split_screen=True), split_screen=True)
brick.pop_brick_breadcrumb(split_screen=True)
def remove_brick(ifc, brick, library=None, brick_uri=None):
@@ -158,14 +151,13 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
ifc.run("library.remove_reference", reference=reference)
brick.remove_brick(brick_uri)
brick.run_refresh_brick_viewer()
brick.run_refresh_brick_viewer(split_screen=True)
def serialize_brick(brick):
brick.serialize_brick()
def add_namespace(brick, alias=None, uri=None):
def add_brick_namespace(brick, alias=None, uri=None):
brick.add_namespace(alias, uri)
@@ -176,4 +168,3 @@ def set_brick_list_root(brick, brick_root=None, split_screen=False):
def remove_brick_relation(brick, brick_uri=None, predicate=None, object=None):
brick.remove_relation(brick_uri, predicate, object)
brick.run_refresh_brick_viewer()
+51 -14
View File
@@ -23,11 +23,9 @@ def load_resources(resource):
resource.load_resources()
resource.load_resource_properties()
def add_resource(tool_ifc, resource_tool, ifc_class, parent_resource=None):
tool_ifc.run("resource.add_resource", ifc_class=ifc_class, parent_resource=parent_resource)
resource_tool.load_resources()
resource_tool.load_resource_properties()
load_resources(resource_tool)
def load_resource_properties(resource_tool, resource=None):
@@ -56,8 +54,7 @@ def edit_resource(ifc, resource_tool, resource):
def remove_resource(ifc, resource_tool, resource=None):
ifc.run("resource.remove_resource", resource=resource)
resource_tool.load_resources()
resource_tool.load_resource_properties()
load_resources(resource_tool)
def enable_editing_resource_time(ifc_tool, resource_tool, resource):
@@ -79,9 +76,13 @@ def disable_editing_resource_time(resource_tool):
def calculate_resource_work(ifc, resource_tool, resource):
ifc.run("resource.calculate_resource_work", resource=resource)
resource_tool.load_resources()
resource_tool.load_resource_properties()
if resource_tool.get_task_assignments(resource):
ifc.run("resource.calculate_resource_work", resource=resource)
else:
nested_resources = resource_tool.get_nested_resources(resource)
for nested_resource in nested_resources or []:
ifc.run("resource.calculate_resource_work", resource=nested_resource)
load_resources(resource_tool)
def enable_editing_resource_costs(resource_tool, resource):
@@ -142,20 +143,17 @@ def edit_resource_quantity(resource_tool, ifc, physical_quantity=None):
def import_resources(resource_tool, file_path):
resource_tool.import_resources(file_path)
resource_tool.load_resources()
resource_tool.load_resource_properties()
load_resources(resource_tool)
def expand_resource(resource_tool, resource):
resource_tool.expand_resource(resource)
resource_tool.load_resources()
resource_tool.load_resource_properties()
load_resources(resource_tool)
def contract_resource(resource_tool, resource):
resource_tool.contract_resource(resource)
resource_tool.load_resources()
resource_tool.load_resource_properties()
load_resources(resource_tool)
def assign_resource(ifc, spatial, resource=None, products=None):
@@ -182,3 +180,42 @@ def edit_productivity_pset(ifc, resource_tool):
else:
pset = ifc.run("pset.add_pset", product=resource, name="EPset_Productivity")
ifc.run("pset.edit_pset", pset=pset, properties=resource_tool.get_productivity_attributes())
def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None):
metric = resource_tool.has_metric_constraint(resource, "Usage")
if metric:
return print("Must remove existing metric first")
objective = ifc.run("constraint.add_objective")
ifc.run("constraint.edit_objective", objective=objective, attributes={"ObjectiveQualifier": "PARAMETER"})
metric = ifc.run("constraint.add_metric", objective=objective)
ifc.run(
"constraint.edit_metric",
metric=metric,
attributes={
"ConstraintGrade": "HARD",
"Benchmark": "EQUALTO",
},
)
ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path)
ifc.run("constraint.assign_constraint", product=resource, constraint=objective)
def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
constraints = resource_tool.get_constraints(resource)
for constraint in constraints:
metrics = resource_tool.get_metrics(constraint)
for metric in metrics:
reference = resource_tool.get_metric_reference(metric, is_deep=True)
if reference == reference_path:
ifc.run("constraint.remove_metric", metric=metric)
ifc.run("constraint.unassign_constraint", product=resource, constraint=constraint)
ifc.run("constraint.remove_constraint", constraint=constraint)
def go_to_resource(resource_tool, resource):
resource_tool.go_to_resource(resource)
def calculate_resource_usage(ifc, resource_tool, resource):
ifc.run("resource.calculate_resource_usage", resource=resource)
load_resources(resource_tool)
+4 -3
View File
@@ -193,6 +193,7 @@ def edit_task_time(ifc, sequence, task_time=None):
task = sequence.get_active_task()
sequence.load_task_properties(task=task)
sequence.disable_editing_task_time()
sequence.load_resources()
def assign_predecessor(ifc, sequence, task=None):
@@ -465,11 +466,11 @@ def load_animation_color_scheme(sequence, scheme):
sequence.load_animation_color_scheme(scheme)
def highlight_task(sequence, task=None):
def go_to_task(sequence, task=None):
work_schedule = sequence.get_work_schedule(task)
is_work_schedule_active = sequence.is_work_schedule_active(work_schedule)
if is_work_schedule_active:
sequence.highlight_task(task)
sequence.go_to_task(task)
else:
return "Work schedule is not active"
@@ -485,7 +486,7 @@ def highlight_product_related_task(sequence, spatial, product_type=None):
work_schedule = sequence.get_work_schedule(task)
is_work_schedule_active = sequence.is_work_schedule_active(work_schedule)
if is_work_schedule_active:
sequence.highlight_task(task)
sequence.go_to_task(task)
def guess_date_range(sequence, work_schedule=None):
+31 -13
View File
@@ -94,36 +94,46 @@ class Boundary: pass
@interface
class Brick:
def add_brick(cls, namespace, brick_class): pass
def add_brick_breadcrumb(cls): pass
def add_brick(cls, namespace, brick_class, label): pass
def add_brick_breadcrumb(cls, split_screen=False): pass
def add_brick_from_element(cls, element, namespace, brick_class): pass
def add_brickifc_project(cls, namespace): pass
def add_brickifc_reference(cls, brick, element, project): pass
def add_feed(cls, source, destination): pass
def clear_brick_browser(cls): pass
def add_relation(cls, brick_uri, predicate, object): pass
def remove_relation(cls, brick_uri, predicate, object): pass
def clear_brick_browser(cls, split_screen=False): pass
def clear_project(cls): pass
def export_brick_attributes(cls, brick_uri): pass
def get_active_brick_class(cls): pass
def get_active_brick_class(cls, split_screen=False): pass
def get_brick(cls, element): pass
def get_brick_class(cls, element): pass
def get_brick_path(cls): pass
def get_brick_path_name(cls): pass
def get_brickifc_project(cls): pass
def get_convertable_brick_elements(cls): pass
def get_convertable_brick_spaces(cls): pass
def get_convertable_brick_systems(cls): pass
def get_parent_space(cls, space): pass
def get_element_container(cls, element): pass
def get_element_systems(cls, element): pass
def get_element_feeds(cls, element): pass
def get_item_class(cls, item): pass
def get_library_brick_reference(cls, library, brick_uri): pass
def get_namespace(cls, uri): pass
def import_brick_classes(cls, brick_class): pass
def import_brick_items(cls, brick_class): pass
def import_brick_classes(cls, brick_class, split_screen=False): pass
def import_brick_items(cls, brick_class, split_screen=False): pass
def load_brick_file(cls, filepath): pass
def new_brick_file(cls): pass
def pop_brick_breadcrumb(cls): pass
def pop_brick_breadcrumb(cls, split_screen=False): pass
def remove_brick(cls, brick_uri): pass
def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None): pass
def run_refresh_brick_viewer(cls): pass
def run_view_brick_class(cls, brick_class=None): pass
def select_browser_item(cls, item): pass
def set_active_brick_class(cls, brick_class): pass
def run_refresh_brick_viewer(cls, split_screen=False): pass
def run_view_brick_class(cls, brick_class=None, split_screen=False): pass
def select_browser_item(cls, item, split_screen=False): pass
def set_active_brick_class(cls, brick_class, split_screen=False): pass
def serialize_brick(cls): pass
def add_namespace(cls, alias, uri): pass
def clear_breadcrumbs(cls, split_screen=False): pass
@interface
@@ -241,6 +251,7 @@ class Drawing:
def activate_drawing(cls, camera): pass
def add_literal_to_annotation(cls, obj, Literal='Literal', Path='RIGHT', BoxAlignment='bottom-left'): pass
def copy_representation(cls, source, dest): pass
def create_annotation_context(cls, target_view, object_type=None): pass
def create_annotation_object(cls, drawing, object_type): pass
def create_camera(cls, name, matrix): pass
def create_svg_schedule(cls, schedule): pass
@@ -601,7 +612,10 @@ class Resource:
def enable_editing_resource_time(cls, resource): pass
def enable_editing_resource(cls, resource): pass
def expand_resource(cls, resource): pass
def get_constraints(cls, resource): pass
def get_highlighted_resource(cls): pass
def get_metric_reference(cls, metric, is_deep): pass
def get_metrics(cls, constraint): pass
def get_productivity_attributes(cls): pass
def get_productivity(cls, resource, should_inherit): pass
def get_resource_attributes(cls): pass
@@ -610,6 +624,8 @@ class Resource:
def get_resource_quantity_attributes(cls): pass
def get_resource_time_attributes(cls): pass
def get_resource_time(cls, resource): pass
def go_to_resource(cls, resource): pass
def has_metric_constraint(cls, resource): pass
def import_resources(cls, file_path): pass
def load_cost_value_attributes(cls, cost_value): pass
def load_productivity_data(cls): pass
@@ -618,6 +634,7 @@ class Resource:
def load_resource_time_attributes(cls, resource_time): pass
def load_resources(cls): pass
@interface
class Root:
def add_tracked_opening(cls, obj): pass
@@ -728,7 +745,7 @@ class Sequence:
def get_work_time_attributes(cls): pass
def guess_date_range(cls, work_schedule): pass
def has_task_assignments(cls, product, cost_schedule=None): pass
def highlight_task(cls, task): pass
def go_to_task(cls, task): pass
def is_filter_by_active_schedule(cls): pass
def is_work_schedule_active(cls, work_schedule): pass
def load_animation_color_scheme(cls, scheme): pass
@@ -759,6 +776,7 @@ class Sequence:
def show_snapshot(cls, product_states): pass
def update_task_ICOM(cls, task): pass
def update_visualisation_date(cls, start_date, finish_date): pass
def refresh_task_resources(cls): pass
@interface
+18
View File
@@ -20,8 +20,10 @@ import bpy
import json
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.bim
from mathutils import Vector
from pathlib import Path
import addon_utils
VIEWPORT_ATTRIBUTES = [
@@ -588,3 +590,19 @@ class Blender:
child_obj = tool.Blender.get_object_from_guid(child_guid)
if child_obj:
yield child_obj
@classmethod
def get_blenderbim_version(cls):
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
if blenderbim.bim.last_commit_hash != "8888888":
version += f"-{blenderbim.bim.last_commit_hash[:7]}"
return version
+35 -20
View File
@@ -105,16 +105,16 @@ class Brick(blenderbim.core.tool.Brick):
if predicate == "http://www.w3.org/2000/01/rdf-schema#label":
with BrickStore.new_changeset() as cs:
cs.add((URIRef(brick_uri), URIRef(predicate), Literal(object)))
bpy.context.scene.BIMBrickProperties.new_brick_relation_type = BrickStore.relationships[0][0]
bpy.context.scene.BIMBrickProperties.add_relation_failed = False
bpy.context.scene.BIMBrickProperties.new_brick_relation_type = BrickStore.relationships[0]
bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = False
return
query = BrickStore.graph.query("ASK { <{object_uri}> a ?o . }".replace("{object_uri}", object))
if query:
with BrickStore.new_changeset() as cs:
cs.add((URIRef(brick_uri), URIRef(predicate), URIRef(object)))
bpy.context.scene.BIMBrickProperties.add_relation_failed = False
bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = False
else:
bpy.context.scene.BIMBrickProperties.add_relation_failed = True
bpy.context.scene.BIMBrickProperties.add_brick_relation_failed = True
@classmethod
def remove_relation(cls, brick_uri, predicate, object):
@@ -141,10 +141,23 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def export_brick_attributes(cls, brick_uri):
query = BrickStore.graph.query(
"""
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?label {
<{brick_uri}> rdfs:label ?label .
}
LIMIT 1
""".replace("{brick_uri}", brick_uri))
name = None
for row in query:
name = str(row.get("label"))
if not name:
name = brick_uri.split("#")[-1]
if tool.Ifc.get_schema() == "IFC2X3":
return {"ItemReference": brick_uri, "Name": brick_uri.split("#")[-1]}
return {"ItemReference": brick_uri, "Name": name}
else:
return {"Identification": brick_uri, "Name": brick_uri.split("#")[-1]}
return {"Identification": brick_uri, "Name": name}
@classmethod
def get_active_brick_class(cls, split_screen=False):
@@ -204,8 +217,8 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def get_convertable_brick_spaces(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return tool.Ifc.get().by_type("IfcSpatialStructureElement")
return tool.Ifc.get().by_type("IfcSpatialElement")
return set(tool.Ifc.get().by_type("IfcSpatialStructureElement"))
return set(tool.Ifc.get().by_type("IfcSpatialElement"))
@classmethod
def get_convertable_brick_systems(cls):
@@ -337,7 +350,7 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("PROJECT") as cs:
cs.load_file(filepath)
BrickStore.path = filepath
cls.set_last_saved()
BrickStore.set_last_saved()
BrickStore.load_sub_roots()
BrickStore.load_namespaces()
BrickStore.load_entity_classes()
@@ -386,8 +399,8 @@ class Brick(blenderbim.core.tool.Brick):
)
@classmethod
def run_refresh_brick_viewer(cls, split_screen=False):
return blenderbim.core.brick.refresh_brick_viewer(tool.Brick, split_screen)
def run_refresh_brick_viewer(cls):
return blenderbim.core.brick.refresh_brick_viewer(tool.Brick)
@classmethod
def run_view_brick_class(cls, brick_class=None, split_screen=False):
@@ -413,7 +426,7 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def serialize_brick(cls):
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
cls.set_last_saved()
BrickStore.set_last_saved()
@classmethod
def add_namespace(cls, alias, uri):
@@ -427,11 +440,7 @@ class Brick(blenderbim.core.tool.Brick):
else:
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
@classmethod
def set_last_saved(cls):
save = os.path.getmtime(BrickStore.path)
save = datetime.datetime.fromtimestamp(save)
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
class BrickStore:
@@ -512,7 +521,7 @@ class BrickStore:
ignore_namespace = True
break
if not ignore_namespace:
BrickStore.namespaces.append((uri, f"{alias}: {uri}", ""))
BrickStore.namespaces.append((alias, str(uri)))
@classmethod
def load_entity_classes(cls):
@@ -534,7 +543,7 @@ class BrickStore:
)
BrickStore.entity_classes[root_class] = []
for uri in sorted([x[0].toPython() for x in query]):
BrickStore.entity_classes[root_class].append((uri, uri.split("#")[-1], ""))
BrickStore.entity_classes[root_class].append(uri)
@classmethod
def load_relationships(cls):
@@ -548,7 +557,7 @@ class BrickStore:
"""
)
for uri in sorted([x[0].toPython() for x in query]):
BrickStore.relationships.append((uri, uri.split("#")[-1], ""))
BrickStore.relationships.append(uri)
@classmethod
def set_history_size(cls, size):
@@ -590,3 +599,9 @@ class BrickStore:
for i in range(0, total_changesets):
BrickStore.graph.redo()
BrickStore.history.append(total_changesets)
@classmethod
def set_last_saved(cls):
save = os.path.getmtime(BrickStore.path)
save = datetime.datetime.fromtimestamp(save)
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
+5 -5
View File
@@ -475,7 +475,7 @@ class Drawing(blenderbim.core.tool.Drawing):
m[2][2] = -1
m.translation = (x, y, z + 1.6)
return m
return mathutils.Matrix(((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1)))
return mathutils.Matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1)))
elif target_view == "ELEVATION_VIEW":
if location_hint == "NORTH":
return mathutils.Matrix(((-1, 0, 0, x), (0, 0, 1, y), (0, 1, 0, z), (0, 0, 0, 1)))
@@ -1504,7 +1504,7 @@ class Drawing(blenderbim.core.tool.Drawing):
elements = cls.get_elements_in_camera_view(tool.Ifc.get_object(drawing), bpy.data.objects)
include = pset.get("Include", None)
if include:
elements = set(ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements))
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements)
else:
if tool.Ifc.get_schema() == "IFC2X3":
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialStructureElement"))
@@ -1516,7 +1516,7 @@ class Drawing(blenderbim.core.tool.Drawing):
exclude = pset.get("Exclude", None)
if exclude:
elements -= set(ifcopenshell.util.selector.filter_elements(ifc_file, exclude, elements=elements))
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
elements -= set(ifc_file.by_type("IfcOpeningElement"))
return elements
@@ -1529,10 +1529,10 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Ifc.get_object(drawing), [tool.Ifc.get_object(e) for e in ifc_file.by_type("IfcSpace")]
)
if include:
elements = set(ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements))
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include, elements=elements)
exclude = pset.get("Exclude", None)
if exclude:
elements -= set(ifcopenshell.util.selector.filter_elements(ifc_file, exclude, elements=elements))
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
return elements
@classmethod
+12 -2
View File
@@ -396,8 +396,18 @@ class Geometry(blenderbim.core.tool.Geometry):
return f"{representation.ContextOfItems.id()}/{representation.id()}"
@classmethod
def get_styles(cls, obj):
return [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
def get_styles(cls, obj, only_assigned_to_faces=False):
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
if not only_assigned_to_faces:
return styles
usage_count = [0] * len(obj.material_slots)
if not usage_count: # if there are no materials, polygons will still use index 0
return []
for poly in obj.data.polygons:
usage_count[poly.material_index] += 1
styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]
return styles
# TODO: multiple Literals?
@classmethod
+76 -10
View File
@@ -32,7 +32,7 @@ import ifcopenshell.util.date as ifcdateutils
import ifcopenshell.util.cost
import ifcopenshell.util.resource
import blenderbim.bim.schema
import ifcopenshell.util.constraint
class Resource(blenderbim.core.tool.Resource):
@classmethod
@@ -55,12 +55,13 @@ class Resource(blenderbim.core.tool.Resource):
tprops = bpy.context.scene.BIMResourceTreeProperties
tprops.resources.clear()
contracted_resources = json.loads(props.contracted_resources)
props.is_resource_update_enabled = False
for resource in tool.Ifc.get().by_type("IfcResource"):
if not resource.HasContext:
continue
create_new_resource_li(resource, 0)
cls.load_productivity_data()
props.is_resource_update_enabled = True
props.is_editing = True
@classmethod
@@ -71,7 +72,7 @@ class Resource(blenderbim.core.tool.Resource):
for item in tprops.resources:
resource = tool.Ifc.get().by_id(item.ifc_definition_id)
item.name = resource.Name if resource.Name else "Unnamed"
item.schedule_usage = resource.Usage.ScheduleUsage or 1 if resource.Usage else 0
item.schedule_usage = resource.Usage.ScheduleUsage if (resource.Usage and resource.Usage.ScheduleUsage) else 0
props.is_resource_update_enabled = True
@classmethod
@@ -279,6 +280,8 @@ class Resource(blenderbim.core.tool.Resource):
def expand_resource(cls, resource):
props = bpy.context.scene.BIMResourceProperties
contracted_resources = json.loads(props.contracted_resources)
if not resource.id() in contracted_resources:
return
contracted_resources.remove(resource.id())
props.contracted_resources = json.dumps(contracted_resources)
@@ -372,12 +375,75 @@ class Resource(blenderbim.core.tool.Resource):
@classmethod
def edit_productivity_pset(cls, resource, attributes):
productivity = cls.get_productivity(resource)
if productivity:
pset = tool.Ifc.get().by_id(productivity["id"])
else:
pset = tool.Ifc.run("pset.add_pset", product=resource, name="EPset_Productivity")
tool.Ifc.run(
if not productivity:
return
return tool.Ifc.run(
"pset.edit_pset",
pset=pset,
pset= tool.Ifc.get().by_id(productivity["id"]),
properties=attributes,
)
)
@classmethod
def get_constraints(cls, resource):
return ifcopenshell.util.constraint.get_constraints(product=resource)
@classmethod
def get_metrics(cls, constraint):
return ifcopenshell.util.constraint.get_metrics(constraint)
@classmethod
def get_metric_reference(cls, metric, is_deep=True):
return ifcopenshell.util.constraint.get_metric_reference(metric, is_deep=is_deep)
@classmethod
def has_metric_constraint(cls, resource, attribute):
metrics = ifcopenshell.util.constraint.get_metric_constraints(resource, attribute)
return True if metrics else False
@classmethod
def run_edit_resource_time(cls, resource, attributes):
if not resource.Usage:
tool.Ifc.run(
"resource.add_resource_time",
resource=resource,
)
tool.Ifc.run("resource.edit_resource_time", resource_time=resource.Usage, attributes=attributes)
@classmethod
def go_to_resource(cls, resource):
def get_ancestors_ids(resource):
ids = []
for rel in resource.Nests or []:
ids.append(rel.RelatingObject.id())
ids.extend(get_ancestors_ids(rel.RelatingObject))
return ids
ancestors = get_ancestors_ids(resource)
contracted_resources = json.loads(bpy.context.scene.BIMResourceProperties.contracted_resources)
for ancestor in ancestors:
if ancestor in contracted_resources:
contracted_resources.remove(ancestor)
bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources)
cls.load_resources()
cls.load_resource_properties()
resource_props = bpy.context.scene.BIMResourceTreeProperties
expanded_resources = [item.ifc_definition_id for item in resource_props.resources]
bpy.context.scene.BIMResourceProperties.active_resource_index = expanded_resources.index(resource.id())
@classmethod
def run_calculate_resource_usage(cls, resource):
tool.Ifc.run("resource.calculate_resource_usage", resource=resource)
@classmethod
def get_task_assignments(cls, resource):
return ifcopenshell.util.resource.get_task_assignments(resource)
@classmethod
def get_nested_resources(cls, resource):
return ifcopenshell.util.resource.get_nested_resources(resource)
@classmethod
def is_attribute_locked(cls, resource, attribute):
return ifcopenshell.util.constraint.is_attribute_locked(resource, attribute)
+1 -1
View File
@@ -184,7 +184,7 @@ class ImportFilterQueryTransformer(lark.Transformer):
elif args[0].data == "quoted_string":
return args[0].children[0].value[1:-1].replace('\\"', '"')
elif args[0].data == "regex_string":
return args[0].children[0].value
return "/" + args[0].children[0].value + "/"
elif args[0].data == "special":
if args[0].children[0].data == "null":
return "NULL"
+45 -32
View File
@@ -198,8 +198,12 @@ class Sequence(blenderbim.core.tool.Sequence):
item.name = task.Name or "Unnamed"
item.identification = task.Identification or "XXX"
if props.highlighted_task_id:
item.is_predecessor = props.highlighted_task_id in [rel.RelatedProcess.id() for rel in task.IsPredecessorTo]
item.is_successor = props.highlighted_task_id in [rel.RelatingProcess.id() for rel in task.IsSuccessorFrom]
item.is_predecessor = props.highlighted_task_id in [
rel.RelatedProcess.id() for rel in task.IsPredecessorTo
]
item.is_successor = props.highlighted_task_id in [
rel.RelatingProcess.id() for rel in task.IsSuccessorFrom
]
calendar = ifcopenshell.util.sequence.derive_calendar(task)
if task.HasAssignments:
for rel in task.HasAssignments:
@@ -412,11 +416,12 @@ class Sequence(blenderbim.core.tool.Sequence):
new = props.task_resources.add()
new.ifc_definition_id = resource.id()
new.name = resource.Name or "Unnamed"
new.schedule_usage = resource.Usage.ScheduleUsage or 1 if resource.Usage else 0
new.schedule_usage = resource.Usage.ScheduleUsage or 0 if resource.Usage else 0
@classmethod
def load_resources(cls):
blenderbim.core.resource.load_resources(tool.Resource)
cls.refresh_task_resources
@classmethod
def get_task_inputs(cls, task):
@@ -466,9 +471,11 @@ class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def get_highlighted_task(cls):
props = bpy.context.scene.BIMWorkScheduleProperties
task_props = bpy.context.scene.BIMTaskTreeProperties
return tool.Ifc.get().by_id(task_props.tasks[props.active_task_index].ifc_definition_id)
tasks = bpy.context.scene.BIMTaskTreeProperties.tasks
if len(tasks) and len(tasks) > bpy.context.scene.BIMWorkScheduleProperties.active_task_index:
return tool.Ifc.get().by_id(
tasks[bpy.context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id
)
@classmethod
def get_direct_nested_tasks(cls, task):
@@ -483,15 +490,6 @@ class Sequence(blenderbim.core.tool.Sequence):
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
@classmethod
def get_task_resources(cls, task):
resources = []
for rel in task.OperatesOn:
for object in rel.RelatedObjects:
if object.is_a("IfcResource"):
resources.append(object)
return resources
@classmethod
def enable_editing_work_calendar_times(cls, work_calendar):
props = bpy.context.scene.BIMWorkCalendarProperties
@@ -768,25 +766,27 @@ class Sequence(blenderbim.core.tool.Sequence):
)
@classmethod
def highlight_task(cls, task):
def expand_ancestors(task):
def go_to_task(cls, task):
def get_ancestor_ids(task):
ids = []
for rel in task.Nests or []:
parent_task = rel.RelatingObject if rel.RelatingObject.is_a("IfcTask") else None
contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks)
if parent_task and parent_task.id() in contracted_tasks:
contracted_tasks.remove(parent_task.id())
bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks)
expand_ancestors(parent_task)
work_schedule = cls.get_active_work_schedule()
cls.load_task_tree(work_schedule)
cls.load_task_properties()
ids.append(rel.RelatingObject.id())
ids.extend(get_ancestor_ids(rel.RelatingObject))
return ids
contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks)
for ancestor_id in get_ancestor_ids(task):
if ancestor_id in contracted_tasks:
contracted_tasks.remove(ancestor_id)
bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks)
work_schedule = cls.get_active_work_schedule()
cls.load_task_tree(work_schedule)
cls.load_task_properties()
task_props = bpy.context.scene.BIMTaskTreeProperties
displayed_tasks = [item.ifc_definition_id for item in task_props.tasks]
if not task.id() in displayed_tasks:
expand_ancestors(task)
task_index = displayed_tasks.index(task.id()) or 0
bpy.context.scene.BIMWorkScheduleProperties.active_task_index = task_index
expanded_tasks = [item.ifc_definition_id for item in task_props.tasks]
bpy.context.scene.BIMWorkScheduleProperties.active_task_index = expanded_tasks.index(task.id()) or 0
@classmethod
def guess_date_range(cls, work_schedule):
@@ -1667,4 +1667,17 @@ class Sequence(blenderbim.core.tool.Sequence):
resources = cls.get_task_resources(task)
cls.load_task_inputs(inputs)
cls.load_task_outputs(outputs)
cls.load_task_resources(resources)
cls.load_task_resources(resources)
@classmethod
def refresh_task_resources(cls):
task = cls.get_highlighted_task()
if not task:
return
cls.load_task_resources(cls.get_task_resources(task))
@classmethod
def has_duration(cls, task):
if task.TaskTime and task.TaskTime.ScheduleDuration:
return True
return False
+6 -3
View File
@@ -22,12 +22,12 @@ import blenderbim.core.tool
import blenderbim.tool as tool
from blenderbim.bim import import_ifc
import re
from mathutils import Matrix
from mathutils import Matrix, Vector
class System(blenderbim.core.tool.System):
@classmethod
def add_ports(cls, obj, add_start_port=True, add_end_port=True):
def add_ports(cls, obj, add_start_port=True, add_end_port=True, offset_end_port=None):
def add_port(mep_element, matrix):
port = tool.Ifc.run("system.add_port", element=mep_element)
port.FlowDirection = "NOTDEFINED"
@@ -47,7 +47,10 @@ class System(blenderbim.core.tool.System):
if add_start_port:
ports.append(add_port(mep_element, obj.matrix_world @ Matrix()))
if add_end_port:
ports.append(add_port(mep_element, obj.matrix_world @ Matrix.Translation((0, 0, length))))
m = obj.matrix_world @ Matrix.Translation((0, 0, length))
if offset_end_port:
m.translation += offset_end_port
ports.append(add_port(mep_element, m))
return ports
@classmethod
+8 -8
View File
@@ -32,16 +32,16 @@ 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"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "--upgrade", "pytest"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "--upgrade", "pytest-bdd"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "--upgrade", "pytest-blender"])
subprocess.call([py_exec, "-m", "pip", "install", f"--target={sys_paths[-1]}", "--upgrade", "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"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pytest"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pytest-bdd"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pytest-blender"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pygments"])
try:
import pytest
+174 -91
View File
@@ -1,114 +1,203 @@
@brick
Feature: Brick
Scenario: Create Brick project
Given an empty Blender session
And the Brickschema is stubbed
When I press "bim.new_brick_file"
Then nothing happens
Scenario: Load Brick project
Given an empty Blender session
And the Brickschema is stubbed
When I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
Then nothing happens
Scenario: View Brick class
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.view_brick_class(brick_class='Equipment')"
When I press "bim.view_brick_class(brick_class='Building')"
Then nothing happens
Scenario: View Brick item
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.view_brick_item(item='https://brickschema.org/schema/Brick#Chiller')"
When I press "bim.view_brick_item(item='https://example.org/digitaltwin#lighting_zone_1')"
Then nothing happens
Scenario: Rewind brick class
Scenario: Rewind Brick class
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.view_brick_class(brick_class='Equipment')"
When I press "bim.rewind_brick_class()"
And I press "bim.view_brick_class(brick_class='Building')"
When I press "bim.rewind_brick_class"
Then nothing happens
Scenario: Close Brick project
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.close_brick_project"
Then nothing happens
Scenario: Close Brick project then create Brick project
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.close_brick_project"
And the Brickschema is stubbed
When I press "bim.new_brick_file"
Then nothing happens
Scenario: Add Brick - vanilla Brick with no IFC
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.new_brick_file"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I set "scene.BIMBrickProperties.new_brick_label" to "abc123"
When I press "bim.add_brick"
Then nothing happens
Scenario: Add Brick - from geometry without a Brick IFC library
Given an empty IFC project
And the Brickschema is stubbed
And I press "bim.new_brick_file"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
When I press "bim.add_brick"
Then nothing happens
Scenario: Add Brick - from geometry with a Brick IFC library
Given an empty IFC project
And the Brickschema is stubbed
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.convert_brick_project"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
When I press "bim.add_brick"
Then nothing happens
Scenario: Refresh Brick viewer
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.new_brick_file"
When I press "bim.refresh_brick_viewer"
Then nothing happens
Scenario: Add Brick relation - vanilla Brick with no IFC
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I press "bim.view_brick_class(brick_class='Lighting_Zone')"
And I set "scene.BIMBrickProperties.active_brick_index" to "0"
And I set "scene.BIMBrickProperties.brick_create_relations_toggled" to "True"
And I set "scene.BIMBrickProperties.new_brick_relation_object" to "xyz789"
When I press "bim.add_brick_relation"
Then nothing happens
Scenario: Add Brick relation - vanilla Brick with no IFC and with split screen
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I set "scene.BIMBrickProperties.brick_entity_create_type" to "Location"
And I set "scene.BIMBrickProperties.new_brick_label" to "abc123"
And I set "scene.BIMBrickProperties.brick_entity_class" to "https://brickschema.org/schema/Brick#Building"
And I press "bim.add_brick"
And I press "bim.view_brick_class(brick_class='Lighting_Zone')"
And I set "scene.BIMBrickProperties.active_brick_index" to "0"
And I set "scene.BIMBrickProperties.split_screen_toggled" to "True"
And I press "bim.view_brick_class(brick_class='Building', split_screen=True)"
And I set "scene.BIMBrickProperties.split_screen_active_brick_index" to "0"
And I set "scene.BIMBrickProperties.brick_create_relations_toggled" to "True"
When I press "bim.add_brick_relation"
Then nothing happens
Scenario: Remove Brick - vanilla Brick
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.view_brick_class(brick_class='Lighting_Zone')"
And I set "scene.BIMBrickProperties.active_brick_index" to "0"
When I press "bim.remove_brick"
Then nothing happens
Scenario: Remove Brick - with a Brick IFC library reference
Given an empty IFC project
And the Brickschema is stubbed
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I press "bim.add_brick"
And I press "bim.view_brick_class(brick_class='Equipment')"
And I set "scene.BIMBrickProperties.active_brick_index" to "0"
When I press "bim.remove_brick"
Then nothing happens
Scenario: Change viewer list root
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.set_list_root_toggled" to "True"
When I set "scene.BIMBrickProperties.brick_list_root" to "Location"
Then nothing happens
Scenario: Change viewer list root - split screen
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.set_list_root_toggled" to "True"
And I set "scene.BIMBrickProperties.split_screen_toggled" to "True"
When I set "scene.BIMBrickProperties.split_screen_brick_list_root" to "Location"
Then nothing happens
Scenario: Toggle split screen
Given an empty Blender session
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I set "scene.BIMBrickProperties.split_screen_toggled" to "True"
Then nothing happens
Scenario: Set active namespace
Given an empty Blender session
And the Brickschema is stubbed
When I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
Then nothing happens
Scenario: Bind new namespace
Given an empty Blender session
And the Brickschema is stubbed
And I set "scene.BIMBrickProperties.new_brick_namespace_alias" to "digitaltwin2"
And I set "scene.BIMBrickProperties.new_brick_namespace_uri" to "https://example.org/digitaltwin2#"
When I press "bim.add_brick_namespace"
Then nothing happens
Scenario: Convert brick project
Given an empty IFC project
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
When I press "bim.convert_brick_project"
Then nothing happens
Scenario: Assign brick reference
Given an empty IFC project
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I set "scene.BIMBrickProperties.brick_equipment_class" to "https://brickschema.org/schema/Brick#Chiller"
And I press "bim.add_brick"
And I press "bim.view_brick_class(brick_class='Chiller')"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And the object "IfcChiller/Cube" is selected
And I press "bim.convert_brick_project"
When I press "bim.assign_brick_reference"
Then nothing happens
Scenario: Add brick - vanilla brick with no IFC
Given an empty Blender session
And I press "bim.new_brick_file"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
When I press "bim.add_brick"
Then nothing happens
Scenario: Add brick - without a brick IFC library
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
When I press "bim.add_brick"
Then nothing happens
Scenario: Add brick - with a brick IFC library
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.convert_brick_project"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
When I press "bim.add_brick"
Then nothing happens
Scenario: Add brick feed
Given an empty IFC project
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.convert_brick_project"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcUnitaryEquipment"
And I set "scene.BIMRootProperties.ifc_predefined_type" to "AIRHANDLER"
And I press "bim.assign_class"
And I press "bim.add_brick"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcAirTerminalBox"
And I set "scene.BIMRootProperties.ifc_predefined_type" to "VARIABLEFLOWPRESSUREDEPENDANT"
And I press "bim.assign_class"
And I press "bim.add_brick"
And the object "IfcUnitaryEquipment/Cube" is selected
And additionally the object "IfcAirTerminalBox/Cube" is selected
When I press "bim.add_brick_feed"
Then nothing happens
Scenario: Convert IFC to brick
Given an empty IFC project
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I press "bim.convert_brick_project"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
@@ -120,26 +209,20 @@ Scenario: Convert IFC to brick
When I press "bim.convert_ifc_to_brick"
Then nothing happens
Scenario: New brick file
Given an empty Blender session
When I press "bim.new_brick_file"
Then nothing happens
Scenario: Refresh brick viewer
Given an empty Blender session
And I press "bim.new_brick_file"
When I press "bim.refresh_brick_viewer"
Then nothing happens
Scenario: Remove brick - without a brick IFC library
Scenario: Assign brick reference
Given an empty IFC project
And the Brickschema is stubbed
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I set "scene.BIMBrickProperties.brick_entity_class" to "https://brickschema.org/schema/Brick#Chiller"
And I press "bim.add_brick"
And I press "bim.view_brick_class(brick_class='Chiller')"
And I set "scene.BIMBrickProperties.active_brick_index" to "0"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcChiller"
And I press "bim.assign_class"
And I press "bim.load_brick_project(filepath='{cwd}/test/files/spaces.ttl')"
And the object "IfcChiller/Cube" is selected
And I set "scene.BIMBrickProperties.namespace" to "https://example.org/digitaltwin#"
And I press "bim.add_brick"
When I press "bim.remove_brick"
And I press "bim.convert_brick_project"
When I press "bim.assign_brick_reference"
Then nothing happens
@@ -47,6 +47,7 @@ Scenario: Add representation - add a representation with a scale factor applied
And I add a cube
And the object "Cube" is selected
When the object "Cube" is scaled to "2"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" has no scale
@@ -58,6 +59,7 @@ Scenario: Add representation - add a representation with a scale factor removed
And I press "object.duplicate_move_linked"
And the object "Cube" is selected
When the object "Cube" is scaled to "2"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" has no scale
@@ -179,6 +181,7 @@ Scenario: Update representation - updating a tessellation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.update_representation(obj='IfcWall/Cube')"
@@ -188,6 +191,7 @@ Scenario: Update representation - updating a layered extrusion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -318,7 +322,7 @@ Scenario: Override duplicate move - copying a coloured representation
When I duplicate the selected objects
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
And an empty Blender session is started
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc', should_start_fresh_session=False)"
Then the material "Material" colour is "1,0,0,1"
And the object "IfcWall/Cube" has the material "Material"
And the object "IfcWall/Cube.001" has the material "Material"
@@ -48,3 +48,10 @@ Scenario: Split along edge
When I press "bim.split_along_edge"
Then the object "IfcWall/Cube" is an "IfcWall"
And the object "IfcWall/Cube.001" is an "IfcWall"
Scenario: Enabling and disabling IFC Sverchok
Given an empty IFC project
And I press "preferences.addon_enable(module="sverchok")"
And I press "preferences.addon_enable(module="ifcsverchok")"
And I press "preferences.addon_disable(module="sverchok")"
And I press "preferences.addon_disable(module="ifcsverchok")"
@@ -456,7 +456,7 @@ Scenario: Export IFC - with changed style colour synchronised
When the material "Material" colour is set to "1,0,0,1"
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
And an empty Blender session is started
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc', should_start_fresh_session=False)"
Then the material "Material" colour is "1,0,0,1"
Scenario: Export IFC - with changed style element synchronised
@@ -471,5 +471,5 @@ Scenario: Export IFC - with changed style element synchronised
And the material "Material.001" colour is set to "1,0,0,1"
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
And an empty Blender session is started
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc', should_start_fresh_session=False)"
Then the material "Material.001" colour is "1,0,0,1"
@@ -249,7 +249,8 @@ Scenario: Add Productivity data
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
And I set "scene.BIMResourceProperties.active_resource_index" to "1"
And I set "scene.BIMResourceProperties.should_show_productivity" to "True"
And I press "bim.add_productivity_data"
And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True"
And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00"
And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea"
And I set "scene.BIMResourceProductivity.quantity_consumed[0].hours" to "5"
@@ -286,7 +287,8 @@ Scenario: Calculate Resource Work
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
And I set "scene.BIMResourceProperties.active_resource_index" to "1"
And I set "scene.BIMResourceProperties.should_show_productivity" to "True"
And I press "bim.add_productivity_data"
And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True"
And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00"
And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea"
And I set "scene.BIMResourceProductivity.quantity_consumed[0].hours" to "5"
+10
View File
@@ -24,6 +24,7 @@ import ifcopenshell
import blenderbim.tool as tool
import blenderbim.bim
from blenderbim.bim.ifc import IfcStore
from blenderbim.tool.brick import BrickStore
from blenderbim.bim.module.model.data import AuthoringData
from pytest_bdd import scenarios, given, when, then, parsers
from mathutils import Vector
@@ -71,6 +72,8 @@ def an_empty_blender_session():
if len(bpy.data.objects) > 0:
bpy.data.batch_remove(bpy.data.objects)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
if len(bpy.data.materials) > 0:
bpy.data.batch_remove(bpy.data.materials)
# default project settings
bpy.context.scene.unit_settings.system = "METRIC"
@@ -92,6 +95,13 @@ def an_empty_ifc_2x3_project():
bpy.ops.bim.create_project()
@given("the Brickschema is stubbed")
def the_brickschema_is_stubbed():
# This makes things run faster since we don't need to load the entire brick schema
cwd = os.path.dirname(os.path.realpath(__file__))
BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl")
@when("I load a new pset template file")
def i_load_a_new_pset_template_file():
IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files
+150 -57
View File
@@ -23,46 +23,80 @@ from test.core.bootstrap import ifc, brick
class TestLoadBrickProject:
def test_run(self, brick):
brick.load_brick_file("filepath").should_be_called()
brick.import_brick_classes("Class").should_be_called()
brick.set_active_brick_class("Class").should_be_called()
subject.load_brick_project(brick, filepath="filepath")
brick.import_brick_classes("brick_root").should_be_called()
brick.import_brick_classes("brick_root", split_screen=True).should_be_called()
brick.set_active_brick_class("brick_root").should_be_called()
brick.set_active_brick_class("brick_root", split_screen=True).should_be_called()
subject.load_brick_project(brick, filepath="filepath", brick_root="brick_root")
class TestNewBrickFile:
def test_run(self, brick):
brick.new_brick_file().should_be_called()
brick.import_brick_classes("brick_root").should_be_called()
brick.import_brick_classes("brick_root", split_screen=True).should_be_called()
brick.set_active_brick_class("brick_root").should_be_called()
brick.set_active_brick_class("brick_root", split_screen=True).should_be_called()
subject.new_brick_file(brick, brick_root="brick_root")
class TestViewBrickClass:
def test_run(self, brick):
brick.add_brick_breadcrumb().should_be_called()
brick.clear_brick_browser().should_be_called()
brick.import_brick_classes("brick_class").should_be_called()
brick.import_brick_items("brick_class").should_be_called()
brick.set_active_brick_class("brick_class").should_be_called()
subject.view_brick_class(brick, brick_class="brick_class")
brick.add_brick_breadcrumb(split_screen=False).should_be_called()
brick.clear_brick_browser(split_screen=False).should_be_called()
brick.import_brick_classes("brick_class", split_screen=False).should_be_called()
brick.import_brick_items("brick_class", split_screen=False).should_be_called()
brick.set_active_brick_class("brick_class", split_screen=False).should_be_called()
subject.view_brick_class(brick, brick_class="brick_class", split_screen=False)
def test_split_screen(self, brick):
brick.add_brick_breadcrumb(split_screen=True).should_be_called()
brick.clear_brick_browser(split_screen=True).should_be_called()
brick.import_brick_classes("brick_class", split_screen=True).should_be_called()
brick.import_brick_items("brick_class", split_screen=True).should_be_called()
brick.set_active_brick_class("brick_class", split_screen=True).should_be_called()
subject.view_brick_class(brick, brick_class="brick_class", split_screen=True)
class TestViewBrickItem:
def test_run(self, brick):
brick.add_brick_breadcrumb().should_be_called()
brick.clear_brick_browser().should_be_called()
brick.get_item_class("item").should_be_called().will_return("brick_class")
brick.import_brick_classes("brick_class").should_be_called()
brick.import_brick_items("brick_class").should_be_called()
brick.set_active_brick_class("brick_class").should_be_called()
brick.select_browser_item("item").should_be_called()
subject.view_brick_item(brick, item="item")
brick.run_view_brick_class(brick_class="brick_class", split_screen=False).should_be_called()
brick.select_browser_item("item", split_screen=False).should_be_called()
subject.view_brick_item(brick, item="item", split_screen=False)
def test_split_screen(self, brick):
brick.get_item_class("item").should_be_called().will_return("brick_class")
brick.run_view_brick_class(brick_class="brick_class", split_screen=True).should_be_called()
brick.select_browser_item("item", split_screen=True).should_be_called()
subject.view_brick_item(brick, item="item", split_screen=True)
class TestRewindBrickClass:
def test_run(self, brick):
brick.pop_brick_breadcrumb().should_be_called().will_return("previous_class")
brick.clear_brick_browser().should_be_called()
brick.import_brick_classes("previous_class").should_be_called()
brick.import_brick_items("previous_class").should_be_called()
brick.set_active_brick_class("previous_class").should_be_called()
subject.rewind_brick_class(brick)
brick.pop_brick_breadcrumb(split_screen=False).should_be_called().will_return("previous_class")
brick.clear_brick_browser(split_screen=False).should_be_called()
brick.import_brick_classes("previous_class", split_screen=False).should_be_called()
brick.import_brick_items("previous_class", split_screen=False).should_be_called()
brick.set_active_brick_class("previous_class", split_screen=False).should_be_called()
subject.rewind_brick_class(brick, split_screen=False)
def test_split_screen(self, brick):
brick.pop_brick_breadcrumb(split_screen=True).should_be_called().will_return("previous_class")
brick.clear_brick_browser(split_screen=True).should_be_called()
brick.import_brick_classes("previous_class", split_screen=True).should_be_called()
brick.import_brick_items("previous_class", split_screen=True).should_be_called()
brick.set_active_brick_class("previous_class", split_screen=True).should_be_called()
subject.rewind_brick_class(brick, split_screen=True)
class TestCloseBrickProject:
def test_run(self, brick):
brick.clear_project().should_be_called()
brick.clear_brick_browser().should_be_called()
brick.clear_brick_browser(split_screen=True).should_be_called()
brick.clear_breadcrumbs().should_be_called()
brick.clear_breadcrumbs(split_screen=True).should_be_called()
subject.close_brick_project(brick)
@@ -86,84 +120,113 @@ class TestConvertBrickProject:
class TestAssignBrickReference:
def test_assigning_to_a_new_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return(None)
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return(None)
ifc.run("library.add_reference", library="library").should_be_called().will_return("reference")
brick.export_brick_attributes("brick").should_be_called().will_return("attributes")
brick.export_brick_attributes("brick_uri").should_be_called().will_return("attributes")
ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called()
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_assigning_to_an_existing_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return("reference")
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_adding_a_brickifc_project_if_it_doesnt_exist(self, ifc, brick):
brick.get_library_brick_reference("library", "brick").should_be_called().will_return("reference")
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return(None)
brick.get_namespace("brick").should_be_called().will_return("namespace")
brick.get_namespace("brick_uri").should_be_called().will_return("namespace")
brick.add_brickifc_project("namespace").should_be_called().will_return("project")
brick.add_brickifc_reference("brick", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
class TestAddBrick:
def test_adding_a_brick_from_an_element(self, ifc, brick):
brick.add_brick_from_element("element", "namespace", "brick_class").should_be_called().will_return("brick_uri")
brick.run_refresh_brick_viewer().should_be_called()
subject.add_brick(ifc, brick, element="element", namespace="namespace", brick_class="brick_class", library=None)
subject.add_brick(
ifc, brick, element="element", namespace="namespace", brick_class="brick_class", library=None, label="label"
)
def test_adding_a_brick_an_auto_assigning_it_to_the_ifc_element(self, ifc, brick):
def test_adding_a_brick_and_auto_assigning_it_to_the_ifc_element(self, ifc, brick):
brick.add_brick_from_element("element", "namespace", "brick_class").should_be_called().will_return("brick_uri")
brick.run_assign_brick_reference(element="element", library="library", brick_uri="brick_uri").should_be_called()
brick.run_refresh_brick_viewer().should_be_called()
subject.add_brick(
ifc, brick, element="element", namespace="namespace", brick_class="brick_class", library="library"
ifc, brick, element="element", namespace="namespace", brick_class="brick_class", library="library", label="label"
)
def test_adding_a_plain_brick(self, ifc, brick):
brick.add_brick("namespace", "brick_class").should_be_called()
brick.add_brick("namespace", "brick_class", "label").should_be_called()
brick.run_refresh_brick_viewer().should_be_called()
subject.add_brick(ifc, brick, element=None, namespace="namespace", brick_class="brick_class", library=None)
subject.add_brick(ifc, brick, element=None, namespace="namespace", brick_class="brick_class", library=None, label="label")
class TestAddBrickFeed:
class TestAddBrickRelation:
def test_run(self, ifc, brick):
brick.get_brick("source").should_be_called().will_return("source_brick")
brick.get_brick("destination").should_be_called().will_return("destination_brick")
brick.add_feed("source_brick", "destination_brick").should_be_called()
brick.add_relation("brick_uri", "predicate", "object").should_be_called()
brick.run_refresh_brick_viewer().should_be_called()
subject.add_brick_feed(ifc, brick, source="source", destination="destination")
subject.add_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object")
class TestConvertIfcToBrick:
def test_run(self, brick):
brick.get_convertable_brick_elements().should_be_called().will_return(["element"])
brick.get_brick_class("element").should_be_called().will_return("brick_class")
brick.add_brick_from_element("element", "namespace", "brick_class").should_be_called().will_return("brick_uri")
brick.run_assign_brick_reference(element="element", library="library", brick_uri="brick_uri").should_be_called()
brick.get_convertable_brick_spaces().should_be_called().will_return({"space", "parent"})
brick.get_brick_class("space").should_be_called().will_return("space_class")
brick.add_brick_from_element("space", "namespace", "space_class").should_be_called().will_return("space_uri")
brick.run_assign_brick_reference(element="space", library="library", brick_uri="space_uri").should_be_called()
brick.get_brick_class("parent").should_be_called().will_return("parent_class")
brick.add_brick_from_element("parent", "namespace", "parent_class").should_be_called().will_return("parent_uri")
brick.run_assign_brick_reference(element="parent", library="library", brick_uri="parent_uri").should_be_called()
brick.get_parent_space("space").should_be_called().will_return("parent")
brick.get_parent_space("parent").should_be_called().will_return(None)
brick.add_relation("parent_uri", "https://brickschema.org/schema/Brick#hasPart", "space_uri").should_be_called()
brick.get_convertable_brick_systems().should_be_called().will_return({"system"})
brick.get_brick_class("system").should_be_called().will_return("system_class")
brick.add_brick_from_element("system", "namespace", "system_class").should_be_called().will_return("system_uri")
brick.run_assign_brick_reference(element="system", library="library", brick_uri="system_uri").should_be_called()
brick.get_convertable_brick_elements().should_be_called().will_return({"element", "downstream_element"})
brick.get_brick_class("element").should_be_called().will_return("element_class")
brick.add_brick_from_element("element", "namespace", "element_class").should_be_called().will_return("element_uri")
brick.get_element_container("element").should_be_called().will_return("space")
brick.add_relation("element_uri", "https://brickschema.org/schema/Brick#hasLocation", "space_uri").should_be_called()
brick.get_element_systems("element").should_be_called().will_return(["system"])
brick.add_relation("system_uri", "https://brickschema.org/schema/Brick#hasPart", "element_uri").should_be_called()
brick.run_assign_brick_reference(element="element", library="library", brick_uri="element_uri").should_be_called()
brick.get_brick_class("downstream_element").should_be_called().will_return("downstream_element_class")
brick.add_brick_from_element("downstream_element", "namespace", "downstream_element_class").should_be_called().will_return("downstream_element_uri")
brick.get_element_container("downstream_element").should_be_called().will_return("space")
brick.add_relation("downstream_element_uri", "https://brickschema.org/schema/Brick#hasLocation", "space_uri").should_be_called()
brick.get_element_systems("downstream_element").should_be_called().will_return(["system"])
brick.add_relation("system_uri", "https://brickschema.org/schema/Brick#hasPart", "downstream_element_uri").should_be_called()
brick.run_assign_brick_reference(element="downstream_element", library="library", brick_uri="downstream_element_uri").should_be_called()
brick.get_element_feeds("element").should_be_called().will_return(["downstream_element"])
brick.get_element_feeds("downstream_element").should_be_called().will_return([])
brick.add_relation("element_uri", "https://brickschema.org/schema/Brick#feeds", "downstream_element_uri").should_be_called()
brick.run_refresh_brick_viewer().should_be_called()
subject.convert_ifc_to_brick(brick, namespace="namespace", library="library")
class TestNewBrickFile:
def test_run(self, brick):
brick.new_brick_file().should_be_called()
brick.import_brick_classes("Class").should_be_called()
brick.set_active_brick_class("Class").should_be_called()
subject.new_brick_file(brick)
class TestRefreshBrickViewer:
def test_run(self, brick):
brick.get_active_brick_class().should_be_called().will_return("class")
brick.run_view_brick_class(brick_class="class").should_be_called()
brick.get_active_brick_class().should_be_called().will_return("brick_class")
brick.run_view_brick_class(brick_class="brick_class").should_be_called()
brick.pop_brick_breadcrumb().should_be_called()
brick.get_active_brick_class(split_screen=True).should_be_called().will_return("brick_class")
brick.run_view_brick_class(brick_class="brick_class", split_screen=True).should_be_called()
brick.pop_brick_breadcrumb(split_screen=True).should_be_called()
subject.refresh_brick_viewer(brick)
@@ -185,3 +248,33 @@ class TestRemoveBrick:
brick.remove_brick("brick_uri").should_be_called()
brick.run_refresh_brick_viewer().should_be_called()
subject.remove_brick(ifc, brick, library=None, brick_uri="brick_uri")
class TestSerializeBrick:
def test_run(self, brick):
brick.serialize_brick().should_be_called()
subject.serialize_brick(brick)
class TestAddBrickNamespace:
def test_run(self, brick):
brick.add_namespace("alias", "uri").should_be_called()
subject.add_brick_namespace(brick, alias="alias", uri="uri")
class TestSetBrickListRoot:
def test_run(self, brick):
brick.run_view_brick_class(brick_class="brick_root", split_screen=False).should_be_called()
brick.clear_breadcrumbs(split_screen=False).should_be_called()
subject.set_brick_list_root(brick, brick_root="brick_root", split_screen=False)
def test_split_screen(self, brick):
brick.run_view_brick_class(brick_class="brick_root", split_screen=True).should_be_called()
brick.clear_breadcrumbs(split_screen=True).should_be_called()
subject.set_brick_list_root(brick, brick_root="brick_root", split_screen=True)
class TestRemoveBrickRelation:
def test_run(self, brick):
brick.remove_relation("brick_uri", "predicate", "object").should_be_called()
subject.remove_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object")
+20 -3
View File
@@ -490,7 +490,7 @@ class TestUpdateDrawingName:
)
ifc.resolve_uri("relative_layout_uri").should_be_called().will_return("absolute_layout_uri")
drawing.does_file_exist("absolute_layout_uri").should_be_called().will_return(True)
drawing.update_embedded_svg_location("absolute_layout_uri", "old_location", "new_location").should_be_called()
drawing.update_embedded_svg_location("absolute_layout_uri", "reference_with_old_location", "new_uri").should_be_called()
drawing.is_editing_sheets().should_be_called().will_return(True)
drawing.import_sheets().should_be_called()
@@ -500,9 +500,9 @@ class TestUpdateDrawingName:
class TestAddAnnotation:
def test_run(self, ifc, collector, drawing):
drawing.show_decorations().should_be_called()
drawing.get_drawing_target_view("drawing").should_be_called().will_return("target_view")
drawing.get_annotation_context("target_view", "object_type").should_be_called().will_return("context")
drawing.show_decorations().should_be_called()
drawing.create_annotation_object("drawing", "object_type").should_be_called().will_return("obj")
ifc.get_entity("obj").should_be_called().will_return(None)
drawing.get_ifc_representation_class("object_type").should_be_called().will_return("ifc_representation_class")
@@ -520,7 +520,24 @@ class TestAddAnnotation:
drawing.enable_editing("obj").should_be_called()
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
def test_do_not_add_without_an_annotation_context(self, ifc, collector, drawing):
def test_create_a_missing_annotation_context_on_the_fly(self, ifc, collector, drawing):
drawing.get_drawing_target_view("drawing").should_be_called().will_return("target_view")
drawing.get_annotation_context("target_view", "object_type").should_be_called().will_return(None)
drawing.create_annotation_context("target_view", "object_type").should_be_called().will_return("context")
drawing.show_decorations().should_be_called()
drawing.create_annotation_object("drawing", "object_type").should_be_called().will_return("obj")
ifc.get_entity("obj").should_be_called().will_return(None)
drawing.get_ifc_representation_class("object_type").should_be_called().will_return("ifc_representation_class")
drawing.run_root_assign_class(
obj="obj",
ifc_class="IfcAnnotation",
predefined_type="object_type",
should_add_representation=True,
context="context",
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", products=["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")
+13
View File
@@ -22,3 +22,16 @@ brick:Location a owl:Class ;
brick:Building a owl:Class ;
rdfs:subClassOf brick:Class, brick:Location .
brick:Chiller a owl:Class,
sh:NodeShape ;
rdfs:subClassOf brick:HVAC_Equipment .
brick:HVAC_Equipment a owl:Class,
sh:NodeShape ;
rdfs:subClassOf brick:Equipment .
brick:Equipment a owl:Class,
sh:NodeShape ;
rdfs:subClassOf brick:Class,
brick:Entity .
+213 -53
View File
@@ -19,6 +19,8 @@
import os
import bpy
import brickschema
import brickschema.persistent
from brickschema.namespaces import REF, A
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
@@ -36,17 +38,17 @@ class TestImplementsTool(NewFile):
class TestAddBrick(NewFile):
def test_run(self):
BrickStore.graph = brickschema.Graph()
result = subject.add_brick("https://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment")
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brick(
"https://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "label"
)
assert "https://example.org/digitaltwin#" in result
assert list(
BrickStore.graph.triples(
(URIRef(result), RDF.type, URIRef("https://brickschema.org/schema/Brick#Equipment"))
)
BrickStore.graph.triples((URIRef(result), A, URIRef("https://brickschema.org/schema/Brick#Equipment")))
)
assert list(
BrickStore.graph.triples(
(URIRef(result), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed"))
(URIRef(result), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("label"))
)
)
@@ -59,6 +61,13 @@ class TestAddBrickBreadcrumb(NewFile):
subject.add_brick_breadcrumb()
assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[1].name == "brick_class"
def test_run_split_screen(self):
subject.set_active_brick_class("brick_class", split_screen=True)
subject.add_brick_breadcrumb(split_screen=True)
assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[0].name == "brick_class"
subject.add_brick_breadcrumb(split_screen=True)
assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[1].name == "brick_class"
class TestAddBrickFromElement(NewFile):
def test_run(self):
@@ -66,14 +75,14 @@ class TestAddBrickFromElement(NewFile):
element = ifc.createIfcChiller()
element.Name = "Chiller"
element.GlobalId = ifcopenshell.guid.new()
BrickStore.graph = brickschema.Graph()
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brick_from_element(
element, "http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment"
)
uri = f"http://example.org/digitaltwin#{element.GlobalId}"
assert result == uri
assert list(
BrickStore.graph.triples((URIRef(uri), RDF.type, URIRef("https://brickschema.org/schema/Brick#Equipment")))
BrickStore.graph.triples((URIRef(uri), A, URIRef("https://brickschema.org/schema/Brick#Equipment")))
)
assert list(
BrickStore.graph.triples(
@@ -81,6 +90,25 @@ class TestAddBrickFromElement(NewFile):
)
)
def test_run_no_element_name(self):
ifc = ifcopenshell.file()
element = ifc.createIfcChiller()
element.GlobalId = ifcopenshell.guid.new()
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brick_from_element(
element, "http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment"
)
uri = f"http://example.org/digitaltwin#{element.GlobalId}"
assert result == uri
assert list(
BrickStore.graph.triples((URIRef(uri), A, URIRef("https://brickschema.org/schema/Brick#Equipment")))
)
assert list(
BrickStore.graph.triples(
(URIRef(uri), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed"))
)
)
class TestAddBrickifcProject(NewFile):
def test_run(self):
@@ -88,66 +116,70 @@ class TestAddBrickifcProject(NewFile):
tool.Ifc.set(ifc)
project = ifc.createIfcProject(ifcopenshell.guid.new())
project.Name = "My Project"
BrickStore.graph = brickschema.Graph()
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brickifc_project("http://example.org/digitaltwin#")
assert result == f"http://example.org/digitaltwin#{project.GlobalId}"
brick = URIRef(result)
assert list(BrickStore.graph.triples((brick, A, REF.ifcProject)))
assert list(BrickStore.graph.triples((brick, REF.ifcProjectID, Literal(project.GlobalId))))
assert list(
BrickStore.graph.triples((brick, RDF.type, URIRef("https://brickschema.org/extension/ifc#Project")))
BrickStore.graph.triples((brick, REF.ifcFileLocation, Literal(bpy.context.scene.BIMProperties.ifc_file)))
)
assert list(
BrickStore.graph.triples(
(brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("My Project"))
)
)
assert list(
BrickStore.graph.triples(
(brick, URIRef("https://brickschema.org/extension/ifc#projectID"), Literal(project.GlobalId))
)
)
assert list(
BrickStore.graph.triples(
(
brick,
URIRef("https://brickschema.org/extension/ifc#fileLocation"),
Literal(bpy.context.scene.BIMProperties.ifc_file),
)
)
)
class TestAddBrickifcReference(NewFile):
def test_run(self):
TestAddBrickifcProject().test_run()
element = tool.Ifc.get().createIfcChiller(ifcopenshell.guid.new())
element.Name = "Chiller"
project = URIRef(f"http://example.org/digitaltwin#{tool.Ifc.get().by_type('IfcProject')[0].GlobalId}")
subject.add_brickifc_reference("http://example.org/digitaltwin#foo", element, project)
brick = URIRef("http://example.org/digitaltwin#foo")
bnode = list(
BrickStore.graph.triples((brick, URIRef("https://brickschema.org/extension/ifc#hasIFCReference"), None))
)[0][2]
assert list(
BrickStore.graph.triples(
(bnode, URIRef("https://brickschema.org/extension/ifc#hasProjectReference"), project)
)
)
assert list(
BrickStore.graph.triples(
(bnode, URIRef("https://brickschema.org/extension/ifc#globalID"), Literal(element.GlobalId))
)
)
bnode = list(BrickStore.graph.triples((brick, A, REF.IFCReference)))
assert list(BrickStore.graph.triples((bnode, REF.hasIfcProjectReference, URIRef(project))))
assert list(BrickStore.graph.triples((bnode, REF.ifcGlobalID, Literal(element.GlobalId))))
assert list(BrickStore.graph.triples((bnode, REF.ifcName, Literal(element.Name))))
class TestAddFeed(NewFile):
class TestAddRelation(NewFile):
def test_run(self):
BrickStore.graph = brickschema.Graph()
subject.add_feed("http://example.org/digitaltwin#source", "http://example.org/digitaltwin#destination")
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
source = subject.add_brick(
"http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "source"
)
destination = subject.add_brick(
"http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "destination"
)
subject.add_relation(source, "https://brickschema.org/schema/Brick#feeds", destination)
assert list(
BrickStore.graph.triples(
(
URIRef("http://example.org/digitaltwin#source"),
URIRef(source),
URIRef("https://brickschema.org/schema/Brick#feeds"),
URIRef("http://example.org/digitaltwin#destination"),
URIRef(destination),
)
)
)
class TestRemoveRelation(NewFile):
def test_run(self):
TestAddRelation().test_run()
source, relation, destination = list(
BrickStore.graph.triples((None, URIRef("https://brickschema.org/schema/Brick#feeds"), None))
)[0]
subject.remove_relation(source, relation, destination)
assert not list(
BrickStore.graph.triples(
(
URIRef(source),
URIRef(relation),
URIRef(destination),
)
)
)
@@ -159,30 +191,43 @@ class TestClearBrickBrowser(NewFile):
subject.clear_brick_browser()
assert len(bpy.context.scene.BIMBrickProperties.bricks) == 0
def test_run_split_screen(self):
bpy.context.scene.BIMBrickProperties.split_screen_bricks.add()
subject.clear_brick_browser(split_screen=True)
assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 0
class TestClearProject(NewFile):
def test_run(self):
BrickStore.graph = "graph"
bpy.context.scene.BIMBrickProperties.active_brick_class == "brick_class"
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add().name = "foo"
bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "brick_class2"
subject.clear_project()
assert BrickStore.graph is None
assert bpy.context.scene.BIMBrickProperties.active_brick_class == ""
assert len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) == 0
assert bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == ""
class TestExportBrickAttributes(NewFile):
def test_run(self):
assert subject.export_brick_attributes("http://example.org/digitaltwin#floor") == {
"Identification": "http://example.org/digitaltwin#floor",
"Name": "floor",
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
brick = subject.add_brick(
"https://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "name"
)
assert subject.export_brick_attributes(brick) == {
"Identification": brick,
"Name": "name",
}
def test_run_ifc2x3(self):
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
brick = subject.add_brick(
"https://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "name"
)
tool.Ifc.set(ifcopenshell.file(schema="IFC2X3"))
assert subject.export_brick_attributes("http://example.org/digitaltwin#floor") == {
"ItemReference": "http://example.org/digitaltwin#floor",
"Name": "floor",
assert subject.export_brick_attributes(brick) == {
"ItemReference": brick,
"Name": "name",
}
@@ -191,6 +236,10 @@ class TestGetActiveBrickClass(NewFile):
subject.set_active_brick_class("brick_class")
assert subject.get_active_brick_class() == "brick_class"
def test_run_split_screen(self):
subject.set_active_brick_class("brick_class", split_screen=True)
assert subject.get_active_brick_class(split_screen=True) == "brick_class"
class TestGetBrick(NewFile):
def test_run(self):
@@ -247,6 +296,51 @@ class TestGetConvertableBrickElements(NewFile):
assert subject.get_convertable_brick_elements() == {element}
class TestGetConvertableBrickSpaces(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcBuildingStorey()
ifc.createIfcWall()
assert subject.get_convertable_brick_spaces() == {element}
class TestGetConvertableBrickSystems(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcSystem()
ifc.createIfcWall()
assert subject.get_convertable_brick_systems() == {element}
class TestGetParentSpace(NewFile):
def test_run(cls):
ifc = ifcopenshell.file()
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingStorey")
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace")
project = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)
ifcopenshell.api.run("aggregate.assign_object", ifc, product=element, relating_object=project)
assert subject.get_parent_space(subelement) == element
assert subject.get_parent_space(element) is None
class TestGetElementContainer(NewFile):
def test_nothing(cls):
pass
class TestGetElementSystems(NewFile):
def test_nothing(cls):
pass
class TestGetElementFeeds(NewFile):
def test_run(cls):
pass
class TestGetItemClass(NewFile):
def test_run(self):
TestLoadBrickFile().test_run()
@@ -291,6 +385,21 @@ class TestImportBrickClasses(NewFile):
assert brick.total_items == 1
assert not brick.label
def test_run_split_sccreen(self):
TestLoadBrickFile().test_run()
subject.import_brick_classes("Class", split_screen=True)
assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 2
brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[0]
assert brick.name == "Building"
assert brick.uri == "https://brickschema.org/schema/Brick#Building"
assert brick.total_items == 1
assert not brick.label
brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[1]
assert brick.name == "Location"
assert brick.uri == "https://brickschema.org/schema/Brick#Location"
assert brick.total_items == 1
assert not brick.label
class TestImportBrickItems(NewFile):
def test_run(self):
@@ -303,6 +412,16 @@ class TestImportBrickItems(NewFile):
assert brick.uri == "https://example.org/digitaltwin#bldg"
assert brick.total_items == 0
def test_run_split_screen(self):
TestLoadBrickFile().test_run()
subject.import_brick_items("Building", split_screen=True)
assert len(bpy.context.scene.BIMBrickProperties.split_screen_bricks) == 1
brick = bpy.context.scene.BIMBrickProperties.split_screen_bricks[0]
assert brick.name == "bldg"
assert brick.label == "My Building"
assert brick.uri == "https://example.org/digitaltwin#bldg"
assert brick.total_items == 0
class TestLoadBrickFile(NewFile):
def test_run(self):
@@ -315,6 +434,8 @@ class TestLoadBrickFile(NewFile):
filepath = os.path.join(cwd, "..", "files", "spaces.ttl")
subject.load_brick_file(filepath)
assert BrickStore.graph
namespaces = [(ns[0], ns[1].toPython()) for ns in BrickStore.graph.namespaces()]
assert ("brick", "https://brickschema.org/schema/Brick#") in namespaces
class TestNewBrickFile(NewFile):
@@ -340,14 +461,37 @@ class TestPopBrickBreadcrumb(NewFile):
assert len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) == 1
assert bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[0].name == "foo"
def test_run_split_screen(self):
bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.add().name = "foo"
bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.add().name = "bar"
assert subject.pop_brick_breadcrumb(split_screen=True) == "bar"
assert len(bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs) == 1
assert bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs[0].name == "foo"
class TestRemoveBrick(NewFile):
def test_run(self):
BrickStore.graph = brickschema.Graph()
result = subject.add_brick("http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment")
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brick(
"http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "label"
)
subject.remove_brick(result)
assert not list(BrickStore.graph.triples((URIRef(result), None, None)))
def test_run_with_bnode(self):
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
result = subject.add_brick(
"http://example.org/digitaltwin#", "https://brickschema.org/schema/Brick#Equipment", "label"
)
TestAddBrickifcProject().test_run()
element = tool.Ifc.get().createIfcChiller(ifcopenshell.guid.new())
element.Name = "Chiller"
project = URIRef(f"http://example.org/digitaltwin#{tool.Ifc.get().by_type('IfcProject')[0].GlobalId}")
subject.add_brickifc_reference(result, element, project)
subject.remove_brick(result)
assert not list(BrickStore.graph.triples((URIRef(result), None, None)))
assert not list(BrickStore.graph.triples((None, REF.hasIfcProjectReference, None)))
class TestRunAssignBrickReference(NewFile):
def test_nothing(self):
@@ -359,7 +503,7 @@ class TestRunRefreshBrickViewer(NewFile):
pass
class TestViewBrickClass(NewFile):
class TestRunViewBrickClass(NewFile):
def test_nothing(self):
pass
@@ -369,6 +513,10 @@ class TestSelectBrowserItem(NewFile):
subject.set_active_brick_class("brick_class")
assert bpy.context.scene.BIMBrickProperties.active_brick_class == "brick_class"
def test_run(self):
subject.set_active_brick_class("brick_class", split_screen=True)
assert bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == "brick_class"
class TestSetActiveBrickClass(NewFile):
def test_run(self):
@@ -376,3 +524,15 @@ class TestSetActiveBrickClass(NewFile):
bpy.context.scene.BIMBrickProperties.bricks.add().name = "bar"
subject.select_browser_item("namespace#bar")
assert bpy.context.scene.BIMBrickProperties.active_brick_index == 1
class TestSerializeBrick(NewFile):
def test_nothing(self):
pass
class TestAddNamespace(NewFile):
def test_run(self):
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
subject.add_namespace("digitaltwin", "http://example.org/digitaltwin")
assert ("digitaltwin", "http://example.org/digitaltwin") in BrickStore.namespaces
+14 -5
View File
@@ -376,7 +376,7 @@ class TestGenerateDrawingMatrix(NewFile):
def test_creating_an_rcp_at_the_origin(self):
assert subject.generate_drawing_matrix("REFLECTED_PLAN_VIEW", None) == mathutils.Matrix(
((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1))
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1))
)
def test_creating_an_rcp_at_the_cursor_at_a_storey(self):
@@ -389,7 +389,7 @@ class TestGenerateDrawingMatrix(NewFile):
obj.matrix_world[2][3] = 3
bpy.context.scene.cursor.location = (1.0, 2.0, 0.0)
assert subject.generate_drawing_matrix("REFLECTED_PLAN_VIEW", element.id()) == mathutils.Matrix(
((-1, 0, 0, 1), (0, 1, 0, 2), (0, 0, -1, 3 + 1.6), (0, 0, 0, 1))
((1, 0, 0, 1), (0, 1, 0, 2), (0, 0, -1, 3 + 1.6), (0, 0, 0, 1))
)
def test_creating_a_north_elevation_at_the_cursor(self):
@@ -485,9 +485,12 @@ class TestImportDrawings(NewFile):
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
subject.import_drawings()
props = bpy.context.scene.DocProperties
assert props.drawings[0].ifc_definition_id == drawing.id()
assert props.drawings[0].name == "FOOBAR"
assert props.drawings[0].target_view == "PLAN_VIEW"
for d in props.drawings:
d.is_expanded = True
subject.import_drawings()
assert props.drawings[1].target_view == "PLAN_VIEW"
assert props.drawings[2].ifc_definition_id == drawing.id()
assert props.drawings[2].name == "FOOBAR"
class TestImportSchedules(NewFile):
@@ -668,6 +671,7 @@ class TestDrawingMaintainingSheetPosition(NewFile):
return drawing_data
def test_run(self):
props = bpy.context.scene.DocProperties
bpy.ops.bim.create_project()
ifc = tool.Ifc.get()
sheet_path = Path.cwd() / "layouts" / "A00 - UNTITLED.svg"
@@ -676,9 +680,14 @@ class TestDrawingMaintainingSheetPosition(NewFile):
bpy.ops.bim.add_sheet()
bpy.ops.bim.load_drawings()
for d in props.drawings:
d.is_expanded = True
bpy.ops.bim.add_drawing()
drawing = ifc.by_type("IfcAnnotation")[0]
for i, d in enumerate(props.drawings):
if d.ifc_definition_id == drawing.id():
props.active_drawing_index = i
bpy.ops.bim.activate_drawing(drawing=drawing.id())
bpy.ops.bim.create_drawing()
bpy.ops.bim.add_drawing_to_sheet()
+4 -3
View File
@@ -160,11 +160,12 @@ class TestSetObjectName(NewFile):
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
element = ifc.createIfcWall()
subject.set_object_name(obj, element)
assert obj.name == "IfcWall/Object"
assert obj.name == "IfcWall/Unnamed"
def test_existing_ifc_prefixes_are_not_repeated(self):
def test_existing_blender_names_are_ignored(self):
ifc = ifcopenshell.file()
obj = bpy.data.objects.new("IfcSlab/Object", bpy.data.meshes.new("Mesh"))
element = ifc.createIfcWall()
element.Name = "Foobar"
subject.set_object_name(obj, element)
assert obj.name == "IfcWall/Object"
assert obj.name == "IfcWall/Foobar"
+2 -2
View File
@@ -115,7 +115,7 @@ class IfcCsv:
self.group_results(groups, attributes)
self.summarise_results(summaries, attributes)
self.sort_results(sort, attributes)
self.sort_results(sort, attributes, include_global_id)
if format == "csv":
self.export_csv(output, delimiter=delimiter)
@@ -225,7 +225,7 @@ class IfcCsv:
self.summaries[si] = max(summary_values[si])
self.summaries[si] = summary_type.title() + ": " + str(self.summaries[si])
def sort_results(self, sort, attributes):
def sort_results(self, sort, attributes, include_global_id):
if sort:
def natural_sort(value):
if isinstance(value, str):
+2 -2
View File
@@ -47,7 +47,7 @@ In this example, we'll extract out all `IfcWall` elements.
::
$ ifcpatch -i input.ifc -o output.ifc -r ExtractElements -a ".IfcWall"
$ ifcpatch -i input.ifc -o output.ifc -r ExtractElements -a "IfcWall"
$ cat output.ifc
Here is a minimal example of how to use IfcPatch as a library:
@@ -60,7 +60,7 @@ Here is a minimal example of how to use IfcPatch as a library:
"input": "input.ifc",
"file": ifcopenshell.open("input.ifc"),
"recipe": "ExtractElements",
"arguments": [".IfcWall"],
"arguments": ["IfcWall"],
})
ifcpatch.write(output, "output.ifc")
@@ -58,4 +58,5 @@ class Usecase:
if self.settings["objective"]:
benchmark_values = list(self.settings["objective"].BenchmarkValues or [])
benchmark_values.append(metric)
self.settings["objective"].BenchmarkValues = benchmark_values
return metric
@@ -0,0 +1,45 @@
# 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/>.
import ifcopenshell
class Usecase:
def __init__(self, file, metric=None, reference_path=None):
"""
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
Used to reference a value of an attribute of an instance through a metric objective entity.
"""
self.file = file
self.settings = {"metric": metric, "reference_path": reference_path}
def execute(self):
if self.settings["reference_path"]:
attributes = self.settings["reference_path"].split(".")
references_created = []
for i in range(len(attributes)):
if i == 0:
reference = self.file.create_entity("IfcReference")
reference.AttributeIdentifier = attributes[i]
self.settings["metric"].ReferencePath = reference
references_created.append(reference)
else:
reference = self.file.create_entity("IfcReference")
reference.AttributeIdentifier = attributes[i]
references_created[i-1].InnerReference = reference
references_created.append(reference)
return references_created
@@ -43,6 +43,10 @@ class Usecase:
self.settings = {"metric": metric}
def execute(self):
if self.settings["metric"].ReferencePath:
reference = self.settings["metric"].ReferencePath
self.delete_reference(reference)
self.file.remove(self.settings["metric"])
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if not rel.RelatingConstraint:
@@ -50,3 +54,8 @@ class Usecase:
for resource_rel in self.file.by_type("IfcResourceConstraintRelationship"):
if not resource_rel.RelatingConstraint:
self.file.remove(resource_rel)
def delete_reference(self, reference):
if reference.InnerReference:
self.delete_reference(reference.InnerReference)
self.file.remove(reference)
@@ -50,9 +50,7 @@ class Usecase:
for rel in self.settings["information"].IsPointer or []:
for information in rel.RelatedDocuments:
ifcopenshell.api.run("document.remove_information", self.file, information=information)
self.file.remove(rel)
# remove IfcDocumentInformationRelationship so it won't become invalid
for rel in self.settings["information"].IsPointedTo or []:
if rel.RelatedDocuments == (self.settings["information"],):
self.file.remove(rel)
@@ -0,0 +1,64 @@
# 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/>.
import math
import ifcopenshell.api
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.resource
class Usecase:
def __init__(self, file, resource=None):
"""Calculates the number of resources required to perform scheduled work on a task.
"""
self.file = file
self.settings = {"resource": resource}
def execute(self):
if ifcopenshell.util.constraint.is_attribute_locked(self.settings["resource"], "Usage.ScheduleUsage"):
return
if (
not self.settings["resource"].Usage
or not self.settings["resource"].Usage.ScheduleWork
):
return
task = ifcopenshell.util.resource.get_task_assignments(
self.settings["resource"]
)
if not task or not task.TaskTime:
return
if not task.TaskTime.DurationType or task.TaskTime.DurationType == "WORKTIME":
hours_per_day = 8
else:
hours_per_day = 24
task_duration = ifcopenshell.util.date.ifc2datetime(
task.TaskTime.ScheduleDuration
)
seconds = task_duration.days * hours_per_day * 60 * 60
seconds += task_duration.seconds
person_hours = ifcopenshell.util.date.ifc2datetime(
self.settings["resource"].Usage.ScheduleWork
)
required_resources = person_hours.total_seconds() / seconds
self.settings["resource"].Usage.ScheduleUsage = float(required_resources)
@@ -60,6 +60,8 @@ class Usecase:
self.settings = {"resource": resource}
def execute(self):
if ifcopenshell.util.constraint.is_attribute_locked(self.settings["resource"], "Usage.ScheduleWork"):
return
amount_worked = ifcopenshell.util.resource.get_resource_required_work(
self.settings["resource"]
)
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import datetime
import ifcopenshell.util.date
import ifcopenshell
class Usecase:
@@ -77,6 +77,11 @@ class Usecase:
del self.settings["attributes"]["ActualFinish"]
for name, value in self.settings["attributes"].items():
metrics = ifcopenshell.util.constraint.get_metric_constraints(
self.resource, "Usage." + name
)
if metrics and ifcopenshell.util.constraint.is_hard_constraint(metrics[0]):
continue
if value:
if "Start" in name or "Finish" in name or name == "StatusTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
@@ -87,6 +92,17 @@ class Usecase:
):
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(self.settings["resource_time"], name, value)
if (
name == "ScheduleUsage"
and ifcopenshell.util.constraint.get_metric_constraints(
self.resource, "Usage.ScheduleWork"
)
):
task = ifcopenshell.util.resource.get_task_assignments(self.resource)
if task:
ifcopenshell.api.run(
"sequence.calculate_task_duration", self.file, task=task
)
def get_resource(self):
return [
@@ -125,6 +125,8 @@ class Usecase:
or "ScheduleDuration" in self.settings["attributes"].keys()
):
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=self.task)
if self.settings["task_time"].ScheduleDuration:
self.handle_resource_calculation()
def calculate_finish(self):
finish = ifcopenshell.util.sequence.get_start_or_finish_date(
@@ -173,3 +175,12 @@ class Usecase:
for e in self.file.get_inverse(self.settings["task_time"])
if e.is_a("IfcTask")
][0]
def handle_resource_calculation(self):
resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_deep=False)
for resource in resources:
if ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleWork"):
ifcopenshell.api.run("resource.calculate_resource_usage", self.file, resource=resource)
#TODO: If the duration changes, this implies the productivity rate must change to accomModate the new Schedule Work to be calculated.
# elif ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleUsage"):
# ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource)
@@ -61,6 +61,7 @@ class draw_settings:
include_projection: bool = True
prefilter: bool = True
include_curves: bool = False
unify_inputs: bool = True
def main(settings, files, iterators=None, merge_projection=True, progress_function=DO_NOTHING):
@@ -135,6 +136,7 @@ def main(settings, files, iterators=None, merge_projection=True, progress_functi
sr.setSubtractionSettings(W.ALWAYS)
sr.setUsePrefiltering(settings.prefilter)
sr.setUnifyInputs(settings.unify_inputs)
try:
sh = ["none", "full", "left"].index(settings.storey_heights)
@@ -0,0 +1,75 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2023 Dion Moult, Yassine Oualid <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/>.
#
#
def get_constraints(product):
constraints = []
for rel in product.HasAssociations or []:
if rel.is_a("IfcRelAssociatesConstraint"):
constraints.append(rel.RelatingConstraint)
return constraints
def get_metrics(constraint):
metrics = []
for metric in constraint.BenchmarkValues or []:
metrics.append(metric)
return metrics
def get_metric_reference(metric, is_deep=True):
def get_reference_Attribute(ref, path):
if ref:
if is_deep:
if not path:
path = ref.AttributeIdentifier
else:
path += ".{}".format(ref.AttributeIdentifier) if ref.AttributeIdentifier else ""
return get_reference_Attribute(ref.InnerReference, path)
else:
return ref.AttributeIdentifier
return path
reference = metric.ReferencePath
return get_reference_Attribute(reference, "")
def get_metric_constraints(resource, attribute):
metrics = []
for constraint in get_constraints(resource) or []:
for metric in get_metrics(constraint) or []:
if bool(
get_metric_reference(metric, is_deep=False) == attribute
or get_metric_reference(metric, is_deep=True) == attribute
):
metrics.append(metric)
if metrics:
return metrics
return None
def is_hard_constraint(metric):
if metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO":
return True
def is_attribute_locked(product, attribute):
is_locked = False
metrics = get_metric_constraints(
product, attribute
)
for metric in metrics or []:
if is_hard_constraint(metric):
is_locked = True
return is_locked
@@ -98,7 +98,7 @@ def readable_ifc_duration(string):
final_string += f"{months} m " if months else ""
final_string += f"{weeks} w " if weeks else ""
final_string += f"{days} d " if days else ""
final_string += f"{hours} h " if hours else ""
final_string += f"{round(float(hours),2)} h " if hours else ""
final_string += f"{minutes} m " if minutes else ""
final_string += f"{seconds} s " if seconds else ""
@@ -88,6 +88,7 @@ def get_pset(element, name, prop=None, should_inherit=True):
return type_pset
return value
def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True):
"""Retrieve property sets, their related properties' names & values and ids.
@@ -451,7 +452,6 @@ def get_styles(element):
return styles
def get_elements_by_material(ifc_file, material):
"""Retrieves the elements related to a material.
@@ -475,7 +475,7 @@ def get_elements_by_material(ifc_file, material):
results = set()
for inverse in ifc_file.get_inverse(material):
if inverse.is_a("IfcRelAssociatesMaterial"):
results.update(inverse.RelatedObjects)
results.update(inverse.RelatedObjects or []) # See Revit bug #675
elif inverse.is_a("IfcMaterialLayer"):
for material_set in inverse.ToMaterialLayerSet:
results.update(get_elements_by_material(ifc_file, material_set))
@@ -869,7 +869,7 @@ def unbatch_remove_deep2(ifc_file):
:rtype: ifcopenshell.file.file
"""
ifc_string = ifc_file.to_string()
lines = iter(ifc_string.split('\n'))
lines = iter(ifc_string.split("\n"))
ids_to_delete = iter(sorted([e.id() for e in ifc_file.to_delete]))
id_to_delete = next(ids_to_delete, None)
result = []
@@ -23,14 +23,18 @@ import ifcopenshell.util.date
def get_productivity(resource, should_inherit=True):
productivity = ifcopenshell.util.element.get_psets(resource).get("EPset_Productivity", None)
if should_inherit and not productivity:
# Proposal for Schema - If instance doesn't have any productivity, inherit it's parent's productivity
if not resource.Nests:
return None
else:
parent_resource = resource.Nests[0].RelatingObject
productivity = ifcopenshell.util.element.get_psets(parent_resource).get("EPset_Productivity", None)
#Note: This is not part of the Schema - but it makes sense to inherit from parent
productivity = get_parent_productivity(resource)
return productivity
def get_parent_productivity(resource):
if not resource.Nests:
return
else:
parent_resource = resource.Nests[0].RelatingObject
productivity = ifcopenshell.util.element.get_psets(parent_resource).get("EPset_Productivity", None)
return productivity
def get_unit_consumed(productivity):
duration = productivity.get("BaseQuantityConsumed", None)
@@ -80,6 +84,12 @@ def get_parametric_resource_products(resource):
products.append(rel2.RelatingProduct)
return products
def get_task_assignments(resource):
for rel in resource.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProcess"):
continue
return rel.RelatingProcess
def get_resource_required_work(resource):
productivity = get_productivity(resource)
@@ -102,3 +112,6 @@ def get_resource_required_work(resource):
required_work = total_quantity_to_produce * productivity_ratio
iso_string = f"P{required_work}D"
return iso_string
def get_nested_resources(resource):
return [object for rel in resource.IsNestedBy or [] for object in rel.RelatedObjects]
@@ -42,14 +42,14 @@ filter_elements_grammar = lark.Lark(
location: "location" comparison value
query: "query:" keys comparison value
pset: quoted_string | unquoted_string | regex_string
prop: quoted_string | unquoted_string | regex_string
pset: quoted_string | regex_string | unquoted_string
prop: quoted_string | regex_string | unquoted_string
keys: quoted_string | unquoted_string
attribute_name: /[A-Z]\\w+/
ifc_class: /Ifc\\w+/
value: special | quoted_string | unquoted_string | regex_string
value: special | quoted_string | regex_string | unquoted_string
unquoted_string: /[^.=\\s]+/
quoted_string: ESCAPED_STRING
regex_string: "/" /[^\\/]+/ "/"
@@ -119,7 +119,9 @@ def get_element_value(element, query):
return Selector.get_element_value(element, filter_query["keys"], filter_query["is_regex"])
def filter_elements(ifc_file, query, elements=None):
def filter_elements(ifc_file, query, elements=None, edit_in_place=False):
if elements and not edit_in_place:
elements = elements.copy()
transformer = FacetTransformer(ifc_file, elements)
transformer.transform(filter_elements_grammar.parse(query))
return transformer.get_results()
@@ -426,7 +428,7 @@ class FacetTransformer(lark.Transformer):
value = float(value)
result = element_value == value
elif isinstance(value, re.Pattern):
result = bool(value.match(element_value))
result = bool(value.match(element_value)) if element_value is not None else False
elif value in (None, True, False):
result = element_value is value
return result if comparison == "=" else not result
@@ -248,7 +248,7 @@ def get_task_work_schedule(task):
def get_nested_tasks(task):
return [object for rel in task.IsNestedBy for object in rel.RelatedObjects]
return [object for rel in task.IsNestedBy or [] for object in rel.RelatedObjects]
def get_parent_task(task):
@@ -928,131 +928,7 @@ class ShapeBuilder:
start_offset = V(0, 0, start_length)
end_extrusion_offset = start_offset.copy()
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(self.file)
# TODO: move to separate shape_builder method
# so we could check transition length without creating representation
def get_transition_length(start_half_dim, end_half_dim, angle, profile_offset=None, verbose=True):
"""get the final transition length for two profiles dimensions, angle and XY offset between them,
the difference from `calculate_transition` - `get_transition_length` is making sure
that length will fit both sides of the transition
"""
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
# offsets tend to have bunch of float point garbage
# that can result in errors when we're calculating value for square root below
offset = V(0, 0) if profile_offset is None else round_vector_to_precision(profile_offset, si_conversion)
diff = start_half_dim.xy - end_half_dim.xy
diff = Vector([abs(i) for i in diff])
# TODO: move to separate shape_builder method
# so it could be tested later separately
def calculate_transition(
start_half_dim, end_half_dim, diff, offset, end_profile=False, angle=None, length=None
):
"""will return transition length based on the profile dimension differences and offset.
If `length` is provided will return transition angle"""
if end_profile:
diff, offset = diff.yx, offset.yx
same_dimensions = is_x(diff.length, 0)
a = diff.x + offset.x
b = diff.x - offset.x
if length is None:
if not same_dimensions:
if diff.x == 0:
return 0
t = tan(radians(angle))
h = (a + b + sqrt(a**2 + 4 * a * b * t**2 + 2 * a * b + b**2)) / (2 * t)
length = sqrt(h**2 - offset.y**2)
# TODO: move somewhere to tests?
if verbose:
A = (end_half_dim if end_profile else start_half_dim) * V(1, 0, 0)
end_profile_offset = offset.to_3d() + V(0, 0, length)
D = (start_half_dim if end_profile else end_half_dim) * V(1, 0, 0)
B, C = -A, -D
C += end_profile_offset
D += end_profile_offset
tested_angle = degrees((A - D).angle(B - C))
print(f"II. length = {length}, requested angle = {angle}, tested angle = {tested_angle}")
else:
if is_x(offset.x, 0):
angle = 90 # NOTE: for now we just hardcode the good value for that case
h = start_half_dim.x / tan(radians(angle / 2))
length = sqrt(h**2 - offset.y**2)
if verbose: # TODO: move to tests
O = V(0, 0, 0)
A = V(-start_half_dim.x, 0, length) + offset.to_3d()
B = A * V(-1, 1, 1)
tested_angle = degrees((A - O).angle(B - O))
print(f"I. length = {length}, requested angle = {angle}, tested angle = {tested_angle}")
else:
h = offset.x / tan(radians(angle))
length = sqrt(h**2 - offset.y**2)
if verbose: # TODO: move to tests
A = V(-start_half_dim.x, 0, 0)
H = A + V(0, 0, length)
D = H + offset.to_3d()
tested_angle = degrees((H - A).angle(D - A))
print(
f"III. length = {length}, requested angle = {angle}, tested angle = {tested_angle}"
)
return length
elif angle is None:
# TODO: write some tests here too
if not same_dimensions:
if length == 0:
return 0
h = sqrt(length**2 + offset.y**2)
t = -h * (a + b) / (a * b - h**2)
angle = degrees(atan(t))
else:
h = sqrt(length**2 + offset.y**2)
if is_x(offset.x, 0):
angle = degrees(2 * atan(start_half_dim.x / h))
else:
angle = degrees(atan(offset.x / length))
return angle
print(f"offset = {profile_offset} / {offset}")
print(f"diff = {diff}")
calculation_arguments = (start_half_dim, end_half_dim, diff, offset)
def check_transition(end_profile=False):
length = calculate_transition(*calculation_arguments, angle=angle, end_profile=end_profile)
other_side_angle = calculate_transition(
*calculation_arguments, length=length, end_profile=not end_profile
)
# NOTE: for now we just hardcode the good value for that case
same_dimensions = is_x(diff.length, 0)
if same_dimensions and is_x(offset.y if not end_profile else offset.x, 0):
requested_angle = 90
else:
requested_angle = angle
print(f"other_side_angle = {other_side_angle}, requested_angle = {requested_angle}")
# need to make sure that the worst angle (maximum angle)
# for this transition angle is `requested_angle`
if other_side_angle < requested_angle or is_x(other_side_angle, requested_angle):
print(f"final length = {length}, angle = {requested_angle}, other side angle = {other_side_angle}")
return length
return check_transition() or check_transition(True)
transition_length = get_transition_length(start_half_dim, end_half_dim, angle, profile_offset)
transition_length = self.mep_transition_length(start_half_dim, end_half_dim, angle, profile_offset)
if transition_length is None:
return None, None
@@ -1199,3 +1075,154 @@ class ShapeBuilder:
}
return representation, transition_data
# TODO: move to separate shape_builder method
# so we could check transition length without creating representation
def mep_transition_length(self, start_half_dim, end_half_dim, angle, profile_offset=None, verbose=True):
"""get the final transition length for two profiles dimensions, angle and XY offset between them,
the difference from `calculate_transition` - `get_transition_length` is making sure
that length will fit both sides of the transition
"""
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
# offsets tend to have bunch of float point garbage
# that can result in errors when we're calculating value for square root below
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(self.file)
offset = V(0, 0) if profile_offset is None else round_vector_to_precision(profile_offset, si_conversion)
diff = start_half_dim.xy - end_half_dim.xy
diff = Vector([abs(i) for i in diff])
print(f"offset = {profile_offset} / {offset}")
print(f"diff = {diff}")
calculation_arguments = {
"start_half_dim": start_half_dim,
"end_half_dim": end_half_dim,
"diff": diff,
"offset": offset,
"verbose": verbose,
}
def check_transition(end_profile=False):
length = self.mep_transition_calculate(**calculation_arguments, angle=angle, end_profile=end_profile)
if length is None:
return
other_side_angle = self.mep_transition_calculate(
**calculation_arguments, length=length, end_profile=not end_profile
)
# NOTE: for now we just hardcode the good value for that case
same_dimension = is_x(diff.y if not end_profile else diff.x, 0)
if same_dimension and is_x(offset.y if not end_profile else offset.x, 0):
requested_angle = 90
else:
requested_angle = angle
print(f"other_side_angle = {other_side_angle}, requested_angle = {requested_angle}")
# need to make sure that the worst angle (maximum angle)
# for this transition angle is `requested_angle`
if other_side_angle < requested_angle or is_x(other_side_angle, requested_angle):
print(f"final length = {length}, angle = {requested_angle}, other side angle = {other_side_angle}")
return length
return check_transition() or check_transition(True)
def mep_transition_calculate(
self, start_half_dim, end_half_dim, offset, diff=None, end_profile=False, angle=None, length=None, verbose=True
):
"""will return transition length based on the profile dimension differences and offset.
If `length` is provided will return transition angle"""
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
if diff is None:
diff = start_half_dim.xy - end_half_dim.xy
diff = Vector([abs(i) for i in diff])
if end_profile:
diff, offset = diff.yx, offset.yx
same_dimension = is_x(diff.x, 0)
a = diff.x + offset.x
b = diff.x - offset.x
if length is None:
if not same_dimension:
t = tan(radians(angle))
h0 = a**2 + 4 * a * b * t**2 + 2 * a * b + b**2
# TODO: we might need to specify the exact failing cases in the future
if h0 < 0:
print(
f"B. Coulndn't calculate transition length for angle = {angle}, offset = {offset}, diff = {diff}"
)
return None
h = (a + b + sqrt(h0)) / (2 * t)
length_squared = h**2 - offset.y**2
if length_squared <= 0:
print(f"B. angle = {angle} requires h = {h} which is not possible with y offset = {offset.y}")
return None
length = sqrt(length_squared)
if verbose:
A = (end_half_dim if end_profile else start_half_dim) * V(1, 0, 0)
end_profile_offset = offset.to_3d() + V(0, 0, length)
D = (start_half_dim if end_profile else end_half_dim) * V(1, 0, 0)
B, C = -A, -D
C += end_profile_offset
D += end_profile_offset
tested_angle = degrees((A - D).angle(B - C))
print(f"A. length = {length}, requested angle = {angle}, tested angle = {tested_angle}")
else:
if is_x(offset.x, 0):
angle = 90 # NOTE: for now we just hardcode the good value for that case
h = start_half_dim.x / tan(radians(angle / 2))
length_squared = h**2 - offset.y**2
if length_squared <= 0:
print(f"B. angle = {angle} requires h = {h} which is not possible with y offset = {offset.y}")
return None
length = sqrt(length_squared)
if verbose:
O = V(0, 0, 0)
A = V(-start_half_dim.x, 0, length) + offset.to_3d()
B = A * V(-1, 1, 1)
tested_angle = degrees((A - O).angle(B - O))
print(f"B. length = {length}, requested angle = {angle}, tested angle = {tested_angle}")
else:
h = offset.x / tan(radians(angle))
length_squared = h**2 - offset.y**2
if length_squared <= 0:
print(f"C. angle = {angle} requires h = {h} which is not possible with y offset = {offset.y}")
return None
length = sqrt(length_squared)
if verbose:
A = V(-start_half_dim.x, 0, 0)
H = A + V(0, 0, length)
H.y += offset.y
D = H.copy()
D.x += offset.x
tested_angle = degrees((H - A).angle(D - A))
print(f"C. length = {length}, requested angle = {angle}, tested angle = {tested_angle}")
return length
elif angle is None:
if not same_dimension:
if length == 0:
return 0
h = sqrt(length**2 + offset.y**2)
t = -h * (a + b) / (a * b - h**2)
angle = degrees(atan(t))
else:
h = sqrt(length**2 + offset.y**2)
if is_x(offset.x, 0):
angle = degrees(2 * atan(start_half_dim.x / h))
else:
angle = degrees(atan(offset.x / h))
return angle
@@ -54,7 +54,6 @@ class TestRemoveInformation(test.bootstrap.IFC4):
project = self.file.createIfcProject()
information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
information2 = ifcopenshell.api.run("document.add_information", self.file, parent=information)
ifcopenshell.api.run("document.add_reference", self.file, information=information)
ifcopenshell.api.run("document.add_reference", self.file, information=information2)
ifcopenshell.api.run("document.remove_information", self.file, information=information)
assert len(self.file.by_type("IfcDocumentInformation")) == 0
@@ -18,6 +18,7 @@
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.constraint
class TestCalculateResourceWork(test.bootstrap.IFC4):
@@ -38,10 +38,3 @@ class TestGetBrickTypeIFC2X3(test.bootstrap.IFC2X3):
type_element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcAirTerminalBoxType")
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=type_element)
assert subject.get_brick_type(element) == "https://brickschema.org/schema/Brick#TerminalUnit"
class TestGetBrickElementsIFC4(test.bootstrap.IFC4):
def test_run(self):
element = self.file.createIfcAirTerminalBox()
self.file.createIfcWall()
assert subject.get_brick_elements(self.file) == {element}
@@ -46,5 +46,6 @@ class TestPsetQto:
names = self.pset_qto.get_applicable_names("IfcWall")
assert "Pset_WallCommon" in names
names = self.pset_qto.get_applicable_names("IfcWallType")
assert len(names) == 5
assert len(names) == 6
assert "Pset_WallCommon" in names
assert "Qto_WallBaseQuantities" in names # Backported fix for IFC4
@@ -0,0 +1,164 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
#
# 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/>.
import pytest
import test.bootstrap
import ifcopenshell.api
from ifcopenshell.util.shape_builder import ShapeBuilder, V, is_x
from math import degrees, radians, tan
from mathutils import Vector
class TestCalculateTransitions(test.bootstrap.IFC4):
def calculate_and_test(self, params, length):
end_profile = params["end_profile"]
start_half_dim = params["start_half_dim"]
end_half_dim = params["end_half_dim"]
offset = params["offset"]
offset = offset if not end_profile else offset.yx
angle = params["angle"]
calculated_length = self.builder.mep_transition_calculate(**params)
if length is None:
assert calculated_length is None
return
assert is_x(calculated_length, length)
# angle confirmation methods:
# A - between two profiles of different dimensions
# B - between two profiles of same dimensions, no offset by x
# C - between two profiles of same dimensions, has offset by x
diff = start_half_dim.xy - end_half_dim.xy
same_dimension = is_x(diff.x if not end_profile else diff.y, 0)
if not same_dimension:
confirmation_method = "A"
else:
confirmation_method = "B" if is_x(offset.x, 0) else "C"
if confirmation_method == "A":
A = (end_half_dim if end_profile else start_half_dim) * V(1, 0, 0)
end_profile_offset = offset.to_3d() + V(0, 0, length)
D = (start_half_dim if end_profile else end_half_dim) * V(1, 0, 0)
B, C = -A, -D
C += end_profile_offset
D += end_profile_offset
tested_angle = degrees((A - D).angle(B - C))
assert is_x(tested_angle, angle)
elif confirmation_method == "B":
O = V(0, 0, 0)
A = V(-start_half_dim.x, 0, length) + offset.to_3d()
B = A * V(-1, 1, 1)
tested_angle = degrees((A - O).angle(B - O))
assert is_x(tested_angle, angle)
elif confirmation_method == "C":
A = V(-start_half_dim.x, 0, 0)
H = A + V(0, 0, length)
H.y += offset.y
D = H.copy()
D.x += offset.x
tested_angle = degrees((H - A).angle(D - A))
assert is_x(tested_angle, angle)
angle = self.builder.mep_transition_calculate(**params | {"angle": None, "length": calculated_length})
assert is_x(angle, angle)
def test_mep_transition_same_dims_no_offset(self):
self.builder = ShapeBuilder(self.file)
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(100, 50, 0),
"offset": V(0, 0),
"end_profile": False,
"angle": 90,
"verbose": True,
}
self.calculate_and_test(params, 100)
def test_mep_transition_same_dims_has_x_offset(self):
self.builder = ShapeBuilder(self.file)
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(100, 50, 0),
"offset": V(50, 50),
"end_profile": False,
"angle": 30,
"verbose": True,
}
self.calculate_and_test(params, 70.71068)
def test_mep_transition_same_dims_has_y_offset(self):
self.builder = ShapeBuilder(self.file)
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(100, 50, 0),
"offset": V(0, 50),
"end_profile": False,
"angle": 90,
"verbose": True,
}
self.calculate_and_test(params, 86.60254)
def test_mep_transition_diff_dims_no_offset(self):
self.builder = ShapeBuilder(self.file)
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(50, 100, 0),
"offset": V(0, 0),
"end_profile": False,
"angle": 30,
"verbose": True,
}
self.calculate_and_test(params, 186.60254)
def test_mep_transition_diff_dims_has_x_y_offset(self):
self.builder = ShapeBuilder(self.file)
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(50, 100, 0),
"offset": V(50, 50),
"end_profile": False,
"angle": 30,
"verbose": True,
}
self.calculate_and_test(params, 165.83124)
def test_mep_transition_y_offset_too_big(self):
self.builder = ShapeBuilder(self.file)
# method A
params = {
"start_half_dim": V(100, 50, 0),
"end_half_dim": V(50, 100, 0),
# offset.y > h - 190 > 186.6
"offset": V(0, 190),
"end_profile": False,
"angle": 30,
"verbose": True,
}
self.calculate_and_test(params, None)
# method B
params["end_half_dim"] = V(100, 100, 0)
self.calculate_and_test(params, None)
# method C
params["offset"].x = 10
self.calculate_and_test(params, None)
@@ -37,13 +37,13 @@ class Patcher:
.. code:: python
# Extract all walls
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcWall"]})
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["IfcWall"]})
# Extract all slabs
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcSlab"]})
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["IfcSlab"]})
# Extract all walls and slabs
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": [".IfcWall|.IfcSlab"]})
ifcpatch.execute({"input": model, "recipe": "ExtractElements", "arguments": ["IfcWall, IfcSlab"]})
"""
self.src = src
self.file = file
@@ -59,8 +59,7 @@ class Patcher:
self.owner_history = self.new.add(owner_history)
break
self.add_element(self.file.by_type("IfcProject")[0])
selector = ifcopenshell.util.selector.Selector()
for element in selector.parse(self.file, self.query):
for element in ifcopenshell.util.selector.filter_elements(self.file, self.query):
self.add_element(element)
self.create_spatial_tree()
self.file = self.new
+1 -1
View File
@@ -5,7 +5,7 @@ N.B.! IfcSverchok nodes are WIP. You can experience Blender crashes while using
## Packaged installation[](https://blenderbim.org/docs-python/ifcsverchok/installation.html#packaged-installation "Permalink to this headline")
IfcSverchok is packaged like a regular Blender add-on, so installation is the same as any other Blender add-on. [Download IfcSverchok here](https://blenderbim.org/builds/ifcsverchok-230704.zip).
IfcSverchok is packaged like a regular Blender add-on, so installation is the same as any other Blender add-on. [Download IfcSverchok here](https://blenderbim.org/builds/ifcsverchok-230823.zip).
Like all Blender add-ons, they can be installed using `Edit > Preferences > Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox`. You can enable add-ons permanently by using `Save User Settings` from the Addons menu.
+6 -8
View File
@@ -699,6 +699,11 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) {
IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr;
double elev = p ? p->second : std::numeric_limits<double>::quiet_NaN();
// @todo is it correct to call nameElement() here with a single storey (what if this element spans multiple?)
if (unify_inputs_) {
compound_local = IfcGeom::util::unify(compound_local, 1.e-6);
}
geometry_data data{ compound_local, dash_arrays, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_ || elevation_ref_guid_ || deferred_section_data_) {
@@ -1187,14 +1192,7 @@ void SvgSerializer::write(const geometry_data& data) {
// Iterate over components of compound to have better chance of matching section edges to closed wires
for (; it.More(); it.Next(), ++dash_it) {
const TopoDS_Shape& subshape_before_unification = it.Value();
TopoDS_Shape subshape;
if (unify_inputs_) {
subshape = IfcGeom::util::unify(subshape_before_unification, 1. - 6);
} else {
subshape = subshape_before_unification;
}
const TopoDS_Shape& subshape = it.Value();
Bnd_Box bb;
try {