mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user