Merge remote-tracking branch 'origin/v0.6.0' into v0.7.0

This commit is contained in:
Thomas Krijnen
2021-08-15 20:40:55 +02:00
92 changed files with 2659 additions and 724 deletions
+10 -9
View File
@@ -4,10 +4,10 @@ A simple Python implementation of BCF. The data model is described in `data.py`.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Almost fully supported, except for two requests.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Almost fully supported, except for two requests.
## bcfxml
@@ -73,9 +73,9 @@ auth_methods = foundation_client.get_auth_methods()
# Our library currently only implements the authorization_code flow
if "authorization_code" in auth_methods:
auth_client.login()
foundation_client.login()
bcf_client = BcfClient()
bcf_client = BcfClient(foundation_client)
versions = foundation_client.get_versions()
for version in versions:
@@ -94,7 +94,8 @@ print(data)
```
## Todo List
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
* For `bcfxml.py` two xsds support is remaining namely 'documents.xsd` and `extensions.xsd`.
* For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
- For `bcfxml.py` two xsds support is remaining namely 'documents.xsd`and`extensions.xsd`.
- For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
+1
View File
@@ -139,6 +139,7 @@ class BcfClient:
self.baseurl = version["api_base_url"]
def get(self, endpoint, params=None, is_auth_required=False):
# TODO: handle error http status codes and raise exception. Follow error.json standard.
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
return requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None).json()
-30
View File
@@ -1,30 +0,0 @@
# Server-Test
1. Cd to the server directory i.e cd `IfcOpenShell\src\bcfserver`
2. Set up the server by installing the dependencies
3. Run `pip install -r requirements.txt` to install the dependencies
4. In Python Shell, do the following
- from run import db
- db.create_all() to setup the database table
5. Run `set FLASK_APP=run.py`
6. Run `flask run` to start the server
7. Go to [http://localhost:5000](http://localhost:5000) to see the server
# Register the user
1. Go to http://localhost:5000/register to register the user
2. Create the client
3. For grant type enter authorization_code
4. For response_type enter code secret
5. Enter the scope and create the client
### You will be redirected to the page with the details of your client id and secret
# Foundation API
- Set the Base URL will be `http://127.0.0.1:5000/`
-51
View File
@@ -1,51 +0,0 @@
from flask import jsonify, url_for, redirect, render_template, request, session, flash
from flask_login import login_user, logout_user, login_required, current_user
from flask.blueprints import Blueprint
from website.models import User, OAuth2AuthorizationCode, OAuth2Token, OAuth2Client
from run import app, db
import json
bcf = Blueprint("bcf", __name__, template_folder="templates", url_prefix="/bcf/3.0")
@bcf.route("/projects")
def projects():
Headers = str.split(request.headers["Authorization"])
token = Headers[1]
access_token = OAuth2Token.query.filter_by(access_token=token).first()
print(access_token)
if access_token:
Body = {
"project_id": "F445F4F2-4D02-4B2A-B612-5E456BEF9137",
"name": "Example project 1",
"authorization": {"project_actions": ["createTopic", "createDocument"]},
}, {
"project_id": "A233FBB2-3A3B-EFF4-C123-DE22ABC8414",
"name": "Example project 2",
"authorization": {"project_actions": []},
}
response = app.response_class(
response=json.dumps(Body),
status=200,
mimetype="application/json",
)
return response
else:
message = {"error": "User not recognized"}
response = app.response_class(
response=jsonify(message),
status=200,
mimetype="application/json",
)
return response
@bcf.route("/")
@login_required
def bcf_3():
return "<h1>BCF HOMPAGE</h1>"
@bcf.route("/projects/<project_id>")
def project_details(project_id):
return "Project details"
+1 -1
View File
@@ -69,7 +69,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.6.0-0087fa8-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.6.0-2f3c79a-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -0,0 +1,12 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Parametric.ifc','2020-01-01T00:00:00',(),(),'EPset_Parametric','EPset_Parametric',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('1Ja6GlovjFoeonRLXJwyyl',$,'EPset_Parametric','',.PSET_TYPEDRIVENOVERRIDE.,'IfcTypeObject',(#2,#3));
#2=IFCSIMPLEPROPERTYTEMPLATE('2ZxSvp00j5hxrWv$Q8ajzx',$,'Engine','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('2AN6WomqXCjRkP0g2iwEjp',$,'LayerSetDirection','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,12 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('Qto_TaskBaseQuantities.ifc','2020-01-01T00:00:00',(),(),'Qto_TaskBaseQuantities','Qto_TaskBaseQuantities',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3uZCoCxrr8MA2yVRLzH6P4',$,'Qto_TaskBaseQuantities','',.QTO_OCCURRENCEDRIVEN.,'IfcTask',(#2,#3));
#2=IFCSIMPLEPROPERTYTEMPLATE('0q8nByVuP4cv46Pm4wfGpf',$,'StandardWork','',.Q_TIME.,$,$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('2uf5oHjAz4pRmtn9ihJiAs',$,'OvertimeWork','',.Q_TIME.,$,$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
+18 -16
View File
@@ -38,6 +38,7 @@ def import_attributes(ifc_class, props, data, callback=None):
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)):
callback(attribute.name(), None, data) if callback else None
continue
new = props.add()
new.name = attribute.name()
@@ -65,20 +66,21 @@ def import_attributes(ifc_class, props, data, callback=None):
def export_attributes(props, callback=None):
attributes = {}
for attribute in props:
is_handled_by_callback = callback(attributes, attribute) if callback else False
if attribute.is_null:
attributes[attribute.name] = None
elif is_handled_by_callback:
pass # Our job is done
elif attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "boolean":
attributes[attribute.name] = attribute.bool_value
elif attribute.data_type == "integer":
attributes[attribute.name] = attribute.int_value
elif attribute.data_type == "float":
attributes[attribute.name] = attribute.float_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
for prop in props:
is_handled_by_callback = callback(attributes, prop) if callback else False
if is_handled_by_callback:
continue # Our job is done
if prop.is_null:
attributes[prop.name] = None
elif prop.data_type == "string":
attributes[prop.name] = prop.string_value
elif prop.data_type == "boolean":
attributes[prop.name] = prop.bool_value
elif prop.data_type == "integer":
attributes[prop.name] = prop.int_value
elif prop.data_type == "float":
attributes[prop.name] = prop.float_value
elif prop.data_type == "enum":
attributes[prop.name] = prop.enum_value
return attributes
@@ -26,8 +26,8 @@ classes = (
operator.ExpandCostItem,
operator.ContractCostItem,
operator.RemoveCostItem,
operator.AssignCostItemProduct,
operator.UnassignCostItemProduct,
operator.AssignCostItemQuantity,
operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostValue,
@@ -36,11 +36,15 @@ classes = (
operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts,
operator.ImportCostScheduleCsv,
operator.LoadCostItemQuantities,
prop.CostItem,
prop.CostItemQuantity,
prop.BIMCostProperties,
ui.BIM_PT_cost_schedules,
ui.BIM_PT_cost_item_quantities,
ui.BIM_UL_cost_items,
ui.BIM_UL_cost_columns,
ui.BIM_UL_cost_item_quantities,
)
@@ -8,6 +8,7 @@ from blenderbim.bim.module.cost.prop import purge
from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
class AddCostSchedule(bpy.types.Operator):
@@ -100,22 +101,9 @@ class EnableEditingCostItems(bpy.types.Operator):
def execute(self, context):
if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound:
# lol
# TODO: make pitch higher as costs rise
try:
import aud
device = aud.Device()
# chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
handle = device.play(sound)
sound_buffered = aud.Sound.buffer(sound)
handle_buffered = device.play(sound_buffered)
handle.stop()
handle_buffered.stop()
except:
pass # ah well
self.play_chaching_sound() # lol
self.props = context.scene.BIMCostProperties
self.props.is_cost_update_enabled = False
self.props.active_cost_schedule_id = self.cost_schedule
while len(self.props.cost_items) > 0:
self.props.cost_items.remove(0)
@@ -124,13 +112,31 @@ class EnableEditingCostItems(bpy.types.Operator):
for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.create_new_cost_item_li(related_object_id, 0)
self.props.is_editing = "COST_ITEMS"
self.props.is_cost_update_enabled = True
return {"FINISHED"}
def play_chaching_sound(self):
# TODO: make pitch higher as costs rise
try:
import aud
device = aud.Device()
# chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
handle = device.play(sound)
sound_buffered = aud.Sound.buffer(sound)
handle_buffered = device.play(sound_buffered)
handle.stop()
handle_buffered.stop()
except:
pass # ah well
def create_new_cost_item_li(self, related_object_id, level_index):
cost_item = Data.cost_items[related_object_id]
new = self.props.cost_items.add()
new.ifc_definition_id = related_object_id
new.name = cost_item["Name"] or "Unnamed"
new.identification = cost_item["Identification"] or "XXX"
new.is_expanded = related_object_id not in self.contracted_cost_items
new.level_index = level_index
if cost_item["IsNestedBy"]:
@@ -297,61 +303,81 @@ class EditCostItem(bpy.types.Operator):
return {"FINISHED"}
class AssignCostItemProduct(bpy.types.Operator):
bl_idname = "bim.assign_cost_item_product"
bl_label = "Assign Control"
class AssignCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.assign_cost_item_quantity"
bl_label = "Assign Cost Item Quantity"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
related_object_type: bpy.props.StringProperty()
prop_name: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
self.file = IfcStore.get_file()
if self.related_object_type == "PRODUCT":
products = [
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
for o in context.selected_objects
if o.BIMObjectProperties.ifc_definition_id
]
elif self.related_object_type == "PROCESS":
products = [
self.file.by_id(
context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
)
]
elif self.related_object_type == "RESOURCE":
products = [
self.file.by_id(
context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
)
]
ifcopenshell.api.run(
"cost.assign_cost_item_product",
"cost.assign_cost_item_quantity",
self.file,
cost_item=self.file.by_id(self.cost_item),
products=[
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
for o in related_objects
if o.BIMObjectProperties.ifc_definition_id
],
products=products,
prop_name=self.prop_name,
)
Data.load(self.file)
bpy.ops.bim.load_cost_item_quantities()
return {"FINISHED"}
class UnassignCostItemProduct(bpy.types.Operator):
bl_idname = "bim.unassign_cost_item_product"
bl_label = "Unassign Control"
class UnassignCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.unassign_cost_item_quantity"
bl_label = "Unassign Cost Item Quantity"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
related_object: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
self.file = IfcStore.get_file()
if self.related_object:
products = [self.file.by_id(self.related_object)]
else:
products = [
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
for o in bpy.context.selected_objects
if o.BIMObjectProperties.ifc_definition_id
]
ifcopenshell.api.run(
"cost.unassign_cost_item_product",
"cost.unassign_cost_item_quantity",
self.file,
cost_item=self.file.by_id(self.cost_item),
products=[
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
for o in related_objects
if o.BIMObjectProperties.ifc_definition_id
],
products=products,
)
Data.load(self.file)
bpy.ops.bim.load_cost_item_quantities()
return {"FINISHED"}
@@ -396,21 +422,10 @@ class AddCostItemQuantity(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
self.props = context.scene.BIMCostProperties
if self.props.quantity_types == "QTO":
self.add_quantities_from_qto_filter()
else:
self.add_manual_quantity()
self.add_manual_quantity()
Data.load(self.file)
return {"FINISHED"}
def add_quantities_from_qto_filter(self):
ifcopenshell.api.run(
"cost.assign_cost_item_product_quantities",
self.file,
cost_item=self.file.by_id(self.cost_item),
prop_name=self.props.quantity_names,
)
def add_manual_quantity(self):
ifcopenshell.api.run(
"cost.add_cost_item_quantity",
@@ -557,6 +572,35 @@ class EnableEditingCostItemValue(bpy.types.Operator):
prop.data_type = "float"
prop.float_value = 0.0 if prop.is_null else data[name]
return True
if (
name == "UnitBasis"
and Data.cost_schedules[bpy.context.scene.BIMCostProperties.active_cost_schedule_id]["PredefinedType"]
== "SCHEDULEOFRATES"
):
prop = self.props.cost_value_attributes.add()
prop.name = "UnitBasisValue"
prop.data_type = "float"
prop.is_null = data["UnitBasis"] is None
prop.is_optional = True
if data["UnitBasis"]:
prop.float_value = data["UnitBasis"]["ValueComponent"] or 0
else:
prop.float_value = 0
prop = self.props.cost_value_attributes.add()
prop.name = "UnitBasisUnit"
prop.data_type = "enum"
prop.is_null = prop.is_optional = False
units = {}
for unit_id, unit in UnitData.units.items():
if unit.get("UnitType", None) in ["AREAUNIT", "LENGTHUNIT", "TIMEUNIT", "VOLUMEUNIT", "MASSUNIT"]:
name = unit["Name"]
if unit.get("Prefix", None):
name = f"(unit['Prefix']) {name}"
units[unit_id] = f"{unit['UnitType']} / {name}"
prop.enum_items = json.dumps(units)
if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]:
prop.enum_value = str(data["UnitBasis"]["UnitComponent"])
return True
class DisableEditingCostItemValue(bpy.types.Operator):
@@ -581,7 +625,7 @@ class EditCostValue(bpy.types.Operator):
def _execute(self, context):
props = context.scene.BIMCostProperties
attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes)
attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes, self.export_attributes)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_value",
@@ -592,6 +636,21 @@ class EditCostValue(bpy.types.Operator):
bpy.ops.bim.disable_editing_cost_item_value()
return {"FINISHED"}
def export_attributes(self, attributes, prop):
if prop.name == "UnitBasisValue":
if prop.is_null:
attributes["UnitBasis"] = None
return True
attributes["UnitBasis"] = {
"ValueComponent": prop.float_value or 1,
"UnitComponent": IfcStore.get_file().by_id(
int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
),
}
return True
if prop.name == "UnitBasisUnit":
return True
class CopyCostItemValues(bpy.types.Operator):
bl_idname = "bim.copy_cost_item_values"
@@ -622,7 +681,7 @@ class SelectCostItemProducts(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
related_products = Data.cost_items[self.cost_item]["Controls"]
related_products = Data.cost_items[self.cost_item]["Controls"].keys()
for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products:
@@ -649,7 +708,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
return {"FINISHED"}
def get_related_products(self, cost_item):
self.related_products.extend(cost_item["Controls"])
self.related_products.extend(cost_item["Controls"].keys())
for child_id in cost_item["IsNestedBy"]:
self.get_related_products(Data.cost_items[child_id])
@@ -702,3 +761,35 @@ class RemoveCostColumn(bpy.types.Operator):
Data.set_categories([c.name for c in self.props.columns])
Data.load(IfcStore.get_file())
return {"FINISHED"}
class LoadCostItemQuantities(bpy.types.Operator):
bl_idname = "bim.load_cost_item_quantities"
bl_label = "Load Cost Item Quantities"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
while len(self.props.cost_item_products) > 0:
self.props.cost_item_products.remove(0)
while len(self.props.cost_item_processes) > 0:
self.props.cost_item_processes.remove(0)
while len(self.props.cost_item_resources) > 0:
self.props.cost_item_resources.remove(0)
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items():
related_object = self.file.by_id(control_id)
if related_object.is_a("IfcProduct"):
new = self.props.cost_item_products.add()
elif related_object.is_a("IfcProcess"):
new = self.props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = self.props.cost_item_resources.add()
new.ifc_definition_id = control_id
new.name = related_object.Name or "Unnamed"
total_quantity = 0
for quantity_id in quantity_ids:
total_quantity += self.file.by_id(quantity_id)[3]
new.total_quantity = total_quantity
return {"FINISHED"}
+120 -16
View File
@@ -18,20 +18,35 @@ from bpy.props import (
quantitytypes_enum = []
quantitynames_enum = []
productquantitynames_enum = []
productquantitynames_count = []
processquantitynames_enum = []
processquantitynames_id = 0
resourcequantitynames_enum = []
resourcequantitynames_id = 0
def purge():
global quantitytypes_enum
global quantitynames_enum
global productquantitynames_enum
global productquantitynames_count
global processquantitynames_enum
global processquantitynames_id
global resourcequantitynames_enum
global resourcequantitynames_id
quantitytypes_enum = []
quantitynames_enum = []
productquantitynames_enum = []
productquantitynames_count = []
processquantitynames_enum = []
processquantitynames_id = 0
resourcequantitynames_enum = []
resourcequantitynames_id = 0
def getQuantityTypes(self, context):
global quantitytypes_enum
if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")]
quantitytypes_enum = []
quantitytypes_enum.extend(
[
(t.name(), t.name(), "")
@@ -41,26 +56,99 @@ def getQuantityTypes(self, context):
return quantitytypes_enum
def getQuantityNames(self, context):
global quantitynames_enum
def getProductQuantityNames(self, context):
global productquantitynames_enum
global productquantitynames_count
ifc_file = IfcStore.get_file()
if len(quantitynames_enum) == 0 and ifc_file:
total_selected_objects = len(context.selected_objects)
if total_selected_objects != productquantitynames_count or total_selected_objects == 1:
productquantitynames_enum = []
productquantitynames_count = total_selected_objects
names = set()
for element_id in Data.cost_items[self.active_cost_item_id]["Controls"]:
for obj in context.selected_objects:
element_id = obj.BIMObjectProperties.ifc_definition_id
if not element_id:
continue
potential_names = set()
if element_id not in PsetData.products:
PsetData.load(IfcStore.get_file(), element_id)
PsetData.load(ifc_file, element_id)
for qto_id in PsetData.products[element_id]["qtos"]:
qto = PsetData.qtos[qto_id]
[names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
quantitynames_enum.extend([(n, n, "") for n in names])
return quantitynames_enum
[potential_names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
names = names.intersection(potential_names) if names else potential_names
productquantitynames_enum.extend([(n, n, "") for n in names])
return productquantitynames_enum
def getProcessQuantityNames(self, context):
global processquantitynames_enum
global processquantitynames_id
ifc_file = IfcStore.get_file()
active_task_index = context.scene.BIMWorkScheduleProperties.active_task_index
total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
if not total_tasks or active_task_index >= total_tasks:
return []
ifc_definition_id = context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id
if processquantitynames_id != ifc_definition_id:
processquantitynames_enum = []
processquantitynames_id = ifc_definition_id
names = set()
if ifc_definition_id not in PsetData.products:
PsetData.load(ifc_file, ifc_definition_id)
for qto_id in PsetData.products[ifc_definition_id]["qtos"]:
qto = PsetData.qtos[qto_id]
[names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
processquantitynames_enum.extend([(n, n, "") for n in names])
return processquantitynames_enum
def getResourceQuantityNames(self, context):
global resourcequantitynames_enum
global resourcequantitynames_id
ifc_file = IfcStore.get_file()
active_resource_index = context.scene.BIMResourceProperties.active_resource_index
total_resources = len(context.scene.BIMResourceTreeProperties.resources)
if not total_resources or active_resource_index >= total_resources:
return []
ifc_definition_id = context.scene.BIMResourceTreeProperties.resources[active_resource_index].ifc_definition_id
if resourcequantitynames_id != ifc_definition_id:
resourcequantitynames_enum = []
resourcequantitynames_id = ifc_definition_id
names = set()
if ifc_definition_id not in PsetData.products:
PsetData.load(ifc_file, ifc_definition_id)
for qto_id in PsetData.products[ifc_definition_id]["qtos"]:
qto = PsetData.qtos[qto_id]
[names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
resourcequantitynames_enum.extend([(n, n, "") for n in names])
return resourcequantitynames_enum
def update_cost_item_index(self, context):
bpy.ops.bim.load_cost_item_quantities()
def updateCostItemIdentification(self, context):
props = context.scene.BIMCostProperties
if not props.is_cost_update_enabled or self.identification == "XXX":
return
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}},
)
Data.load(self.file)
if props.active_cost_item_id == self.ifc_definition_id:
attribute = props.cost_item_attributes.get("Identification")
attribute.string_value = self.identification
def updateCostItemName(self, context):
if self.name == "Unnamed":
props = context.scene.BIMCostProperties
if not props.is_cost_update_enabled or self.name == "Unnamed":
return
self.file = IfcStore.get_file()
props = context.scene.BIMCostProperties
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
@@ -74,24 +162,34 @@ def updateCostItemName(self, context):
class CostItem(PropertyGroup):
name: StringProperty(name="Name", update=updateCostItemName)
identification: StringProperty(name="Identification", update=updateCostItemIdentification)
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
class CostItemQuantity(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_quantity: FloatProperty(name="Total Quantity")
class BIMCostProperties(PropertyGroup):
is_cost_update_enabled: BoolProperty(name="Is Cost Update Enabled", default=True)
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing")
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
cost_items: CollectionProperty(name="Cost Items", type=CostItem)
active_cost_item_id: IntProperty(name="Active Cost Id")
cost_item_editing_type: StringProperty(name="Cost Item Editing Type")
active_cost_item_index: IntProperty(name="Active Cost Item Index")
active_cost_item_index: IntProperty(name="Active Cost Item Index", update=update_cost_item_index)
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types")
quantity_names: EnumProperty(items=getQuantityNames, name="Quantity Names")
product_quantity_names: EnumProperty(items=getProductQuantityNames, name="Product Quantity Names")
process_quantity_names: EnumProperty(items=getProcessQuantityNames, name="Process Quantity Names")
resource_quantity_names: EnumProperty(items=getResourceQuantityNames, name="Resource Quantity Names")
active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id")
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
cost_types: EnumProperty(
@@ -109,3 +207,9 @@ class BIMCostProperties(PropertyGroup):
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
columns: CollectionProperty(name="Columns", type=StrProperty)
active_column_index: IntProperty(name="Active Column Index")
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
active_cost_item_product_index: IntProperty(name="Active Cost Item Product Index")
cost_item_processes: CollectionProperty(name="Cost Item Processes", type=CostItemQuantity)
active_cost_item_process_index: IntProperty(name="Active Cost Item Process Index")
cost_item_resources: CollectionProperty(name="Cost Item Resources", type=CostItemQuantity)
active_cost_item_resource_index: IntProperty(name="Active Cost Item Resource Index")
+164 -43
View File
@@ -1,3 +1,4 @@
import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.cost.data import Data
@@ -75,6 +76,32 @@ class BIM_PT_cost_schedules(Panel):
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_editable_cost_item_ui(self, cost_schedule_id):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
if ifc_definition_id:
if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES":
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
op.cost_item = ifc_definition_id
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
if self.props.active_cost_item_id == ifc_definition_id:
if self.props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
else:
op = row.operator("bim.enable_editing_cost_item", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
self.layout.template_list(
"BIM_UL_cost_items",
"",
@@ -108,8 +135,6 @@ class BIM_PT_cost_schedules(Panel):
def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "quantity_types", text="")
if self.props.quantity_types == "QTO":
row.prop(self.props, "quantity_names", text="")
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id
op.ifc_class = self.props.quantity_types
@@ -244,10 +269,112 @@ class BIM_PT_cost_schedules(Panel):
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_PT_cost_item_quantities(Panel):
bl_label = "IFC Cost Item Quantities"
bl_idname = "BIM_PT_cost_item_quantities"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_cost_schedules"
@classmethod
def poll(cls, context):
props = context.scene.BIMCostProperties
total_cost_items = len(props.cost_items)
if total_cost_items > 0 and props.active_cost_item_index < total_cost_items:
return True
return False
def draw(self, context):
self.props = context.scene.BIMCostProperties
cost_item = self.props.cost_items[self.props.active_cost_item_index]
grid = self.layout.grid_flow(columns=3, even_columns=True)
# Column1
col = grid.column()
row2 = col.row(align=True)
row2.label(text="Elements")
op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
op.cost_item = cost_item.ifc_definition_id
row2 = col.row()
row2.template_list(
"BIM_UL_cost_item_quantities",
"",
self.props,
"cost_item_products",
self.props,
"active_cost_item_product_index",
)
row2 = col.row(align=True)
row2.prop(self.props, "product_quantity_names", text="")
op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE")
op.cost_item = cost_item.ifc_definition_id
op.related_object = 0
if CostProp.productquantitynames_enum:
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
op.related_object_type = "PRODUCT"
op.cost_item = cost_item.ifc_definition_id
op.prop_name = self.props.product_quantity_names
# Column2
col = grid.column()
row2 = col.row(align=True)
row2.label(text="Tasks")
row2 = col.row()
row2.template_list(
"BIM_UL_cost_item_quantities",
"",
self.props,
"cost_item_processes",
self.props,
"active_cost_item_process_index",
)
row2 = col.row(align=True)
row2.prop(self.props, "process_quantity_names", text="")
if CostProp.processquantitynames_enum:
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
op.related_object_type = "PROCESS"
op.cost_item = cost_item.ifc_definition_id
op.prop_name = self.props.process_quantity_names
# Column3
col = grid.column()
row2 = col.row(align=True)
row2.label(text="Resources")
row2 = col.row()
row2.template_list(
"BIM_UL_cost_item_quantities",
"",
self.props,
"cost_item_resources",
self.props,
"active_cost_item_resource_index",
)
row2 = col.row(align=True)
row2.prop(self.props, "resource_quantity_names", text="")
if CostProp.resourcequantitynames_enum:
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
op.related_object_type = "RESOURCE"
op.cost_item = cost_item.ifc_definition_id
op.prop_name = self.props.resource_quantity_names
class BIM_UL_cost_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMCostProperties
self.props = context.scene.BIMCostProperties
cost_item = Data.cost_items[item.ifc_definition_id]
row = layout.row(align=True)
@@ -265,51 +392,30 @@ class BIM_UL_cost_items(UIList):
else:
row.label(text="", icon="DOT")
split1 = row.split(factor=0.7)
split1.prop(item, "name", emboss=False, text="")
split1 = row.split(factor=0.1)
split1.prop(item, "identification", emboss=False, text="")
split2 = split1.split(factor=0.5)
split2.alignment = "RIGHT"
split2.prop(item, "name", emboss=False, text="")
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
op.cost_item = item.ifc_definition_id
row.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol']})")
if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES":
split2.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})")
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = item.ifc_definition_id
row.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
for column in props.columns:
row.label(text=str(cost_item["CategoryValues"].get(column.name, "-")))
for column in self.props.columns:
split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-")))
row.label(text="{0:.2f}".format(cost_item["TotalCostValue"]), icon="CON_TRANSLIKE")
split2.label(text="{0:.2f}".format(cost_item["TotalCostValue"]))
self.draw_buttons(split2, item, cost_item)
if context.active_object:
oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True)
if oprops.ifc_definition_id in cost_item["Controls"]:
op = row.operator("bim.unassign_cost_item_product", text="", icon="KEYFRAME_HLT", emboss=False)
op.cost_item = item.ifc_definition_id
else:
op = row.operator("bim.assign_cost_item_product", text="", icon="KEYFRAME", emboss=False)
op.cost_item = item.ifc_definition_id
if props.active_cost_item_id == item.ifc_definition_id:
if props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
elif props.active_cost_item_id:
if props.cost_item_editing_type == "VALUES":
op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN")
op.source = props.active_cost_item_id
op.destination = item.ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
else:
op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
op.cost_item = item.ifc_definition_id
row.operator(
"bim.enable_editing_cost_item", text="", icon="GREASEPENCIL"
).cost_item = item.ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
def draw_buttons(self, row, item, cost_item):
pass # TODO: reimplement somewhere with better UX
# elif self.props.active_cost_item_id:
# if self.props.cost_item_editing_type == "VALUES":
# op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN")
# op.source = self.props.active_cost_item_id
# op.destination = item.ifc_definition_id
class BIM_UL_cost_columns(UIList):
@@ -319,3 +425,18 @@ class BIM_UL_cost_columns(UIList):
row = layout.row(align=True)
row.prop(item, "name", emboss=False, text="")
row.operator("bim.remove_cost_column", text="", icon="X").name = item.name
class BIM_UL_cost_item_quantities(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMCostProperties
cost_item = props.cost_items[props.active_cost_item_index]
if item:
row = layout.row(align=True)
row.split(factor=0.8)
row.label(text=item.name)
row.label(text="{0:.2f}".format(item.total_quantity))
op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X")
op.cost_item = cost_item.ifc_definition_id
op.related_object = item.ifc_definition_id
@@ -120,25 +120,8 @@ class AssignMaterial(bpy.types.Operator):
)
Data.load(IfcStore.get_file())
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
self.set_default_material(obj, element)
return {"FINISHED"}
def set_default_material(self, obj, element):
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in self.file.traverse(element_material) if m.is_a("IfcMaterial")]
if not material:
return
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material[0].id()))
class UnassignMaterial(bpy.types.Operator):
bl_idname = "bim.unassign_material"
@@ -546,25 +529,8 @@ class EditAssignedMaterial(bpy.types.Operator):
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
bpy.ops.bim.disable_editing_assigned_material(obj=obj.name)
self.set_default_material(obj, self.file.by_id(obj.BIMObjectProperties.ifc_definition_id))
return {"FINISHED"}
def set_default_material(self, obj, element):
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in self.file.traverse(element_material) if m.is_a("IfcMaterial")]
if not material:
return
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material[0].id()))
class EnableEditingMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_material_set_item"
@@ -2,6 +2,7 @@ import bpy
from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace
classes = (
product.AddEmptyType,
product.AddTypeInstance,
product.AlignProduct,
product.DynamicallyVoidProduct,
@@ -10,8 +11,7 @@ classes = (
wall.AlignWall,
wall.FlipWall,
wall.SplitWall,
wall.AddWallOpening,
slab.AddSlabOpening,
opening.AddElementOpening,
profile.ExtendProfile,
prop.BIMModelProperties,
ui.BIM_PT_authoring,
@@ -41,6 +41,7 @@ def register():
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
bpy.types.VIEW3D_MT_add.append(product.add_empty_type_button)
bpy.app.handlers.load_post.append(handler.load_post)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
@@ -60,6 +61,7 @@ def unregister():
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
bpy.types.VIEW3D_MT_add.remove(product.add_empty_type_button)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
@@ -1,20 +1,36 @@
import bpy
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.module.model import root, product, wall, slab, profile, opening
from blenderbim.bim.module.model import root, product, wall, slab, profile, opening, task
from blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent
@persistent
def load_post(*args):
ifcopenshell.api.add_pre_listener(
"attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name
)
ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
)
ifcopenshell.api.add_post_listener(
"sequence.edit_task_time", "BlenderBIM.Task.CalculateQuantities", task.calculate_quantities
)
for usecase in [
"material.assign_material",
"material.edit_constituent",
"material.edit_layer",
"material.edit_profile",
"material.add_constituent",
"material.add_layer",
"material.add_profile",
]:
ifcopenshell.api.add_post_listener(
usecase, "BlenderBIM.Product.EnsureMaterialAssigned", product.ensure_material_assigned
)
ifcopenshell.api.add_post_listener(
"material.edit_profile_usage",
"BlenderBIM.Product.RegenerateProfileUsage",
@@ -22,8 +38,10 @@ def load_post(*args):
)
IfcStore.add_element_listener(opening.element_listener)
IfcStore.add_element_listener(wall.element_listener)
IfcStore.add_element_listener(slab.element_listener)
IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
)
@@ -40,7 +58,6 @@ def load_post(*args):
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
)
IfcStore.add_element_listener(slab.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid
)
@@ -57,7 +74,6 @@ def load_post(*args):
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
)
IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
)
@@ -52,6 +52,46 @@ def mode_callback(obj, data):
bm.free()
class AddElementOpening(bpy.types.Operator):
bl_idname = "bim.add_element_opening"
bl_label = "Add Element Opening"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
selected_objs = context.selected_objects
if len(selected_objs) == 0 or not context.active_object:
return {"FINISHED"}
obj = context.active_object
if not obj.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
local_location = obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]:
return {"FINISHED"}
# The opening shall be based on the smallest bounding dimension of the element
dimension = min(obj.dimensions)
bpy.ops.mesh.primitive_cube_add(size=dimension * 2)
opening = context.selected_objects[0]
# Place the opening in the middle of the element
global_location = obj.matrix_world @ raycast[1]
normal = raycast[2]
normal.negate()
global_normal = obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (dimension / 2))
opening.rotation_euler = obj.rotation_euler
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=obj.name)
return {"FINISHED"}
def add_object(self, context):
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=self.size)
@@ -8,6 +8,33 @@ from . import wall, slab, profile
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector, Matrix
from bpy_extras.object_utils import AddObjectHelper
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
bl_idname = "bim.add_empty_type"
bl_label = "Add Empty Type"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.data.objects.new("TYPEX", None)
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
if not [c for c in project.children if "Types" in c.name]:
types = bpy.data.collections.new("Types")
project.collection.children.link(types)
for collection in [c for c in project.children if "Types" in c.name]:
collection.collection.objects.link(obj)
break
break
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
return {"FINISHED"}
def add_empty_type_button(self, context):
self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
class AddTypeInstance(bpy.types.Operator):
@@ -214,3 +241,36 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
bpy.ops.bim.switch_representation(
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
)
def ensure_material_assigned(usecase_path, ifc_file, settings):
if usecase_path == "material.assign_material":
if not settings.get("Material", None):
return
elements = [self.settings["product"]]
else:
elements = []
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
if rel.RelatingMaterial == settings["material"] or [
e for e in ifc_file.traverse(rel.RelatingMaterial) if e == settings["material"]
]:
elements.extend(rel.RelatedObjects)
for element in elements:
obj = IfcStore.get_element(element.GlobalId)
if not obj or not obj.data:
continue
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in ifc_file.traverse(element_material) if m.is_a("IfcMaterial")]
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
continue
obj.data.materials.append(IfcStore.get_element(material[0].id()))
@@ -20,10 +20,9 @@ def element_listener(element, obj):
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
obj.mode != "EDIT"
or not obj.data
not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
@@ -33,27 +32,36 @@ def mode_callback(obj, data):
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
IfcStore.edited_objs.add(obj)
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
if modifier:
return
depth = obj.dimensions.z
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
non_bottom_faces = []
for face in bm.faces:
if face.normal.z > -0.9:
non_bottom_faces.append(face)
else:
face.normal_flip()
bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
bmesh.update_edit_mesh(obj.data)
bm.free()
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = depth
if obj.mode == "EDIT":
IfcStore.edited_objs.add(obj)
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
if modifier:
return
depth = obj.dimensions.z
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
non_bottom_faces = []
for face in bm.faces:
if face.normal.z > -0.9:
non_bottom_faces.append(face)
else:
face.normal_flip()
bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
bmesh.update_edit_mesh(obj.data)
bm.free()
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = depth
else:
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
obj.data.transform(
Matrix.Translation(
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
)
)
obj.matrix_world.translation = new_origin
def ensure_solid(usecase_path, ifc_file, settings):
@@ -199,42 +207,6 @@ def calculate_quantities(usecase_path, ifc_file, settings):
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
class AddSlabOpening(bpy.types.Operator):
bl_idname = "bim.add_slab_opening"
bl_label = "Add Slab Opening"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
selected_objs = context.selected_objects
if len(selected_objs) == 0 or not context.active_object:
return {"FINISHED"}
slab_obj = context.active_object
if not slab_obj.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
slab = IfcStore.get_file().by_id(slab_obj.BIMObjectProperties.ifc_definition_id)
local_location = slab_obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = slab_obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]:
return {"FINISHED"}
bpy.ops.mesh.primitive_cube_add(size=slab_obj.dimensions[2] * 2)
opening = context.selected_objects[0]
# Place the opening in the middle of the slab
global_location = slab_obj.matrix_world @ raycast[1]
normal = raycast[2]
normal.negate()
global_normal = slab_obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (slab_obj.dimensions[2] / 2))
opening.rotation_euler = slab_obj.rotation_euler
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=slab_obj.name)
return {"FINISHED"}
class DumbSlabGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
@@ -249,7 +221,7 @@ class DumbSlabGenerator:
if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break
if not thicknesses:
if not sum(thicknesses):
return
self.collection = bpy.context.view_layer.active_layer_collection.collection
@@ -0,0 +1,28 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.date
from ifcopenshell.api.pset.data import Data as PsetData
def calculate_quantities(usecase_path, ifc_file, settings):
if not set(["ScheduleStart", "ScheduleFinish", "ScheduleDuration"]).intersection(
set(settings["attributes"].keys())
):
return
element = settings["task_time"]
if not element.ScheduleDuration:
return
task = [e for e in ifc_file.get_inverse(element) if e.is_a("IfcTask")][0]
qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities"
)
ifcopenshell.api.run(
"pset.edit_qto",
ifc_file,
should_run_listeners=False,
qto=qto,
properties={
"StandardWork": ifcopenshell.util.date.ifc2datetime(element.ScheduleDuration).days,
},
)
PsetData.load(ifc_file, task.id())
@@ -22,8 +22,7 @@ def element_listener(element, obj):
def mode_callback(obj, data):
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
obj.mode != "EDIT"
or not obj.data
not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
@@ -33,48 +32,21 @@ def mode_callback(obj, data):
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
bpy.ops.bim.dynamically_void_product(obj=obj.name)
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
class AddWallOpening(bpy.types.Operator):
bl_idname = "bim.add_wall_opening"
bl_label = "Add Wall Opening"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
selected_objs = context.selected_objects
if len(selected_objs) == 0 or not context.active_object:
return {"FINISHED"}
wall_obj = context.active_object
if not wall_obj.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
wall = IfcStore.get_file().by_id(wall_obj.BIMObjectProperties.ifc_definition_id)
local_location = wall_obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = wall_obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]:
return {"FINISHED"}
bpy.ops.mesh.primitive_cube_add(size=wall_obj.dimensions[1] * 2)
opening = bpy.context.selected_objects[0]
# Place the opening in the middle of the wall
global_location = wall_obj.matrix_world @ raycast[1]
normal = raycast[2]
normal.negate()
global_normal = wall_obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (wall_obj.dimensions[1] / 2))
opening.rotation_euler = wall_obj.rotation_euler
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=wall_obj.name)
return {"FINISHED"}
if obj.mode == "EDIT":
bpy.ops.bim.dynamically_void_product(obj=obj.name)
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
else:
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
obj.data.transform(
Matrix.Translation(
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
)
)
obj.matrix_world.translation = new_origin
class JoinWall(bpy.types.Operator):
@@ -643,7 +615,7 @@ class DumbWallGenerator:
if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break
if not thicknesses:
if not sum(thicknesses):
return
self.collection = bpy.context.view_layer.active_layer_collection.collection
@@ -960,15 +932,19 @@ class DumbWallPlaner:
IfcStore.edited_objs.add(obj)
def thicken_face(self, face, delta_thickness):
slide_magnitude = abs(delta_thickness) / 2
slide_magnitude = abs(delta_thickness)
for vert in face.verts:
slide_vector = None
for edge in vert.link_edges:
other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0]
if delta_thickness > 0:
potential_slide_vector = vert.co - other_vert.co
if potential_slide_vector.y < 0:
continue
else:
potential_slide_vector = other_vert.co - vert.co
if potential_slide_vector.y > 0:
continue
if abs(potential_slide_vector.x) > 0.9 or abs(potential_slide_vector.z) > 0.9:
continue
slide_vector = potential_slide_vector
@@ -64,14 +64,6 @@ class BimTool(WorkSpaceTool):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Split", icon="EVENT_S")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Opening", icon="EVENT_O")
if props.ifc_class == "IfcSlabType":
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Opening", icon="EVENT_O")
if props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
row = layout.row()
@@ -80,6 +72,10 @@ class BimTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Opening", icon="EVENT_O")
row = layout.row(align=True)
row.label(text="Align")
row = layout.row(align=True)
@@ -142,10 +138,7 @@ class Hotkey(bpy.types.Operator):
bpy.ops.bim.align_product(align_type="NEGATIVE")
def hotkey_S_O(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.add_wall_opening()
elif self.props.ifc_class == "IfcSlabType":
bpy.ops.bim.add_slab_opening()
bpy.ops.bim.add_element_opening()
def hotkey_A_D(self):
bpy.ops.bim.toggle_decomposition_parenting()
@@ -1,9 +1,23 @@
classes = ()
import bpy
from . import ui, prop, operator
classes = (
operator.LoadProfiles,
operator.DisableProfileEditingUI,
operator.RemoveProfileDef,
operator.EnableEditingProfile,
operator.DisableEditingProfile,
operator.EditProfile,
prop.Profile,
prop.BIMProfileProperties,
ui.BIM_PT_profiles,
ui.BIM_UL_profiles,
)
def register():
pass
bpy.types.Scene.BIMProfileProperties = bpy.props.PointerProperty(type=prop.BIMProfileProperties)
def unregister():
pass
del bpy.types.Scene.BIMProfileProperties
@@ -0,0 +1,100 @@
import bpy
import ifcopenshell.api
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.profile.data import Data
class LoadProfiles(bpy.types.Operator):
bl_idname = "bim.load_profiles"
bl_label = "Load Profiles"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMProfileProperties
while len(props.profiles) > 0:
props.profiles.remove(0)
for ifc_definition_id, profile in Data.profiles.items():
new = props.profiles.add()
new.ifc_definition_id = ifc_definition_id
new.name = profile.get("ProfileName", "") or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_profile()
return {"FINISHED"}
class DisableProfileEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_profile_editing_ui"
bl_label = "Disable Profile Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMProfileProperties.is_editing = False
return {"FINISHED"}
class RemoveProfileDef(bpy.types.Operator):
bl_idname = "bim.remove_profile_def"
bl_label = "Remove Profile Definition"
bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMProfileProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("profile.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load(self.file)
bpy.ops.bim.load_profiles()
return {"FINISHED"}
class EnableEditingProfile(bpy.types.Operator):
bl_idname = "bim.enable_editing_profile"
bl_label = "Enable Editing Profile"
bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMProfileProperties
while len(props.profile_attributes) > 0:
props.profile_attributes.remove(0)
data = Data.profiles[self.profile]
blenderbim.bim.helper.import_attributes(data["type"], props.profile_attributes, data)
props.active_profile_id = self.profile
return {"FINISHED"}
class DisableEditingProfile(bpy.types.Operator):
bl_idname = "bim.disable_editing_profile"
bl_label = "Disable Editing Profile"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMProfileProperties.active_profile_id = 0
return {"FINISHED"}
class EditProfile(bpy.types.Operator):
bl_idname = "bim.edit_profile"
bl_label = "Edit Profile"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMProfileProperties
attributes = blenderbim.bim.helper.export_attributes(props.profile_attributes)
self.file = IfcStore.get_file()
profile = self.file.by_id(props.active_profile_id)
ifcopenshell.api.run("profile.edit_profile", self.file, **{"profile": profile, "attributes": attributes})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_profiles()
return {"FINISHED"}
@@ -0,0 +1,29 @@
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class Profile(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMProfileProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
profiles: CollectionProperty(name="Profiles", type=Profile)
active_profile_index: IntProperty(name="Active Profile Index")
active_profile_id: IntProperty(name="Active Profile Id")
profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute)
@@ -0,0 +1,67 @@
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.profile.data import Data
class BIM_PT_profiles(Panel):
bl_label = "IFC Profiles"
bl_idname = "BIM_PT_profiles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
return file
def draw(self, context):
self.file = IfcStore.get_file()
if not Data.is_loaded:
Data.load(self.file)
self.props = context.scene.BIMProfileProperties
row = self.layout.row(align=True)
row.label(text="{} Profiles Found".format(len(Data.profiles)), icon="SNAP_GRID")
if self.props.is_editing:
row.operator("bim.disable_profile_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_profiles", text="", icon="GREASEPENCIL")
if not self.props.is_editing:
return
self.layout.template_list(
"BIM_UL_profiles",
"",
self.props,
"profiles",
self.props,
"active_profile_index",
)
if self.props.active_profile_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout)
class BIM_UL_profiles(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMProfileProperties
if item:
row = layout.row(align=True)
row.label(text=item.name or "Unnamed")
if props.active_profile_id == item.ifc_definition_id:
row.operator("bim.edit_profile", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_profile", text="", icon="CANCEL")
elif props.active_profile_id:
row.operator("bim.remove_profile_def", text="", icon="X").profile = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_profile", text="", icon="GREASEPENCIL")
op.profile = item.ifc_definition_id
row.operator("bim.remove_profile_def", text="", icon="X").profile = item.ifc_definition_id
@@ -172,7 +172,7 @@ class RefreshLibrary(bpy.types.Operator):
self.props.active_library_element = ""
types = IfcStore.library_file.wrapped_data.types_with_super()
for importable_type in ["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule"]:
for importable_type in ["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]:
if importable_type in types:
new = self.props.library_elements.add()
new.name = importable_type
@@ -198,7 +198,10 @@ class ChangeLibraryElement(bpy.types.Operator):
if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
for element in elements:
new = self.props.library_elements.add()
new.name = element.Name or "Unnamed"
if element.is_a("IfcProfileDef"):
new.name = element.ProfileName or "Unnamed"
else:
new.name = element.Name or "Unnamed"
new.ifc_definition_id = element.id()
if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"):
new.is_declared = False
@@ -11,17 +11,30 @@ classes = (
operator.AddQto,
operator.GuessQuantity,
prop.PsetProperties,
prop.MaterialPsetProperties,
prop.TaskPsetProperties,
prop.ResourcePsetProperties,
prop.ProfilePsetProperties,
ui.BIM_PT_object_psets,
ui.BIM_PT_object_qtos,
ui.BIM_PT_material_psets,
ui.BIM_PT_task_qtos,
ui.BIM_PT_resource_qtos,
ui.BIM_PT_profile_psets,
)
def register():
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.MaterialPsetProperties)
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties)
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties)
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.ProfilePsetProperties)
def unregister():
del bpy.types.Object.PsetProperties
del bpy.types.Material.PsetProperties
del bpy.types.Scene.TaskPsetProperties
del bpy.types.Scene.ResourcePsetProperties
del bpy.types.Scene.ProfilePsetProperties
@@ -11,6 +11,43 @@ from ifcopenshell.api.cost.data import Data as CostData
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
def get_pset_props(context, obj, obj_type):
if obj_type == "Object":
obj = bpy.data.objects.get(obj)
return obj.PsetProperties
elif obj_type == "Material":
obj = bpy.data.materials.get(obj)
return obj.PsetProperties
elif obj_type == "Task":
return context.scene.TaskPsetProperties
elif obj_type == "Resource":
return context.scene.ResourcePsetProperties
elif obj_type == "Profile":
return context.scene.ProfilePsetProperties
def get_pset_obj_ifc_definition_id(context, obj, obj_type):
if obj_type == "Object":
obj = bpy.data.objects.get(obj)
return obj.BIMObjectProperties.ifc_definition_id
elif obj_type == "Material":
obj = bpy.data.materials.get(obj)
return obj.BIMObjectProperties.ifc_definition_id
elif obj_type == "Task":
return context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
elif obj_type == "Resource":
return context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
elif obj_type == "Profile":
return context.scene.BIMProfileProperties.profiles[
context.scene.BIMProfileProperties.active_profile_index
].ifc_definition_id
class TogglePsetExpansion(bpy.types.Operator):
bl_idname = "bim.toggle_pset_expansion"
bl_label = "Toggle Pset Expansion"
@@ -32,11 +69,7 @@ class EnablePsetEditing(bpy.types.Operator):
obj_type: bpy.props.StringProperty()
def execute(self, context):
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
self.props = obj.PsetProperties
self.props = get_pset_props(context, self.obj, self.obj_type)
while len(self.props.properties) > 0:
self.props.properties.remove(0)
@@ -138,11 +171,7 @@ class DisablePsetEditing(bpy.types.Operator):
obj_type: bpy.props.StringProperty()
def execute(self, context):
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
props = obj.PsetProperties
props = get_pset_props(context, self.obj, self.obj_type)
props.active_pset_id = 0
return {"FINISHED"}
@@ -161,12 +190,8 @@ class EditPset(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.PsetProperties
props = get_pset_props(context, self.obj, self.obj_type)
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
properties = {}
pset_id = self.pset_id or props.active_pset_id
@@ -213,7 +238,7 @@ class EditPset(bpy.types.Operator):
},
)
CostData.purge()
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
Data.load(IfcStore.get_file(), ifc_definition_id)
bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
return {"FINISHED"}
@@ -231,20 +256,17 @@ class RemovePset(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
props = obj.BIMObjectProperties
props = get_pset_props(context, self.obj, self.obj_type)
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.remove_pset",
self.file,
**{
"product": self.file.by_id(props.ifc_definition_id),
"product": self.file.by_id(ifc_definition_id),
"pset": self.file.by_id(self.pset_id),
},
)
Data.load(IfcStore.get_file(), props.ifc_definition_id)
Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
@@ -254,36 +276,24 @@ class AddPset(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
pset_name: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.PsetProperties
if self.pset_name:
pset_name = self.pset_name
elif self.obj_type == "Object":
pset_name = props.pset_name
elif self.obj_type == "Material":
pset_name = props.material_pset_name
props = get_pset_props(context, self.obj, self.obj_type)
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.add_pset",
self.file,
**{
"product": self.file.by_id(oprops.ifc_definition_id),
"name": pset_name,
"product": self.file.by_id(ifc_definition_id),
"name": props.pset_name,
},
)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
@@ -291,24 +301,25 @@ class AddQto(bpy.types.Operator):
bl_idname = "bim.add_qto"
bl_label = "Add Qto"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.PsetProperties
props = get_pset_props(context, self.obj, self.obj_type)
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.add_qto",
self.file,
**{
"product": self.file.by_id(oprops.ifc_definition_id),
"product": self.file.by_id(ifc_definition_id),
"name": props.qto_name,
},
)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
@@ -52,6 +52,36 @@ def getMaterialPsetNames(self, context):
return psetnames[ifc_class]
def getTaskQtoNames(self, context):
global qtonames
ifc_class = "IfcTask"
if ifc_class not in qtonames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
return qtonames[ifc_class]
def getResourceQtoNames(self, context):
global qtonames
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
if ifc_class not in qtonames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
return qtonames[ifc_class]
def getProfilePsetNames(self, context):
global psetnames
pprops = context.scene.BIMProfileProperties
ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a()
if ifc_class not in psetnames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
return psetnames[ifc_class]
def getQtoNames(self, context):
global qtonames
if "/" in context.active_object.name:
@@ -69,4 +99,31 @@ class PsetProperties(PropertyGroup):
properties: CollectionProperty(name="Properties", type=Attribute)
pset_name: EnumProperty(items=getPsetNames, name="Pset Name")
qto_name: EnumProperty(items=getQtoNames, name="Qto Name")
material_pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
class MaterialPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
class TaskPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
qto_name: EnumProperty(items=getTaskQtoNames, name="Qto Name")
class ResourcePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
qto_name: EnumProperty(items=getResourceQtoNames, name="Qto Name")
class ProfilePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
+121 -10
View File
@@ -3,6 +3,14 @@ from ifcopenshell.api.pset.data import Data
from blenderbim.bim.ifc import IfcStore
def get_active_pset_obj_name(context, obj_type):
if obj_type == "Object":
return context.active_object.name
elif obj_type == "Material":
return context.active_object.active_material.name
return ""
def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
box = layout.box()
row = box.row(align=True)
@@ -10,29 +18,30 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
pset["is_expanded"] = True
icon = "TRIA_DOWN" if pset["is_expanded"] else "TRIA_RIGHT"
row.operator("bim.toggle_pset_expansion", icon=icon, text="", emboss=False).pset_id = pset_id
obj_name = get_active_pset_obj_name(context, obj_type)
if not props.active_pset_id:
row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="")
op.pset_id = pset_id
op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
op.obj = obj_name
op.obj_type = obj_type
op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
op.obj = obj_name
op.obj_type = obj_type
elif props.active_pset_id != pset_id:
row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
op.obj = obj_name
op.obj_type = obj_type
elif props.active_pset_id == pset_id:
row.prop(props, "active_pset_name", icon="COPY_ID", text="")
op = row.operator("bim.edit_pset", icon="CHECKMARK", text="")
op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
op.obj = obj_name
op.obj_type = obj_type
op = row.operator("bim.disable_pset_editing", icon="CANCEL", text="")
op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
op.obj = obj_name
op.obj_type = obj_type
if pset["is_expanded"]:
if props.active_pset_id == pset_id:
@@ -123,7 +132,7 @@ class BIM_PT_object_psets(Panel):
op.obj_type = "Object"
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
for pset_id, pset in sorted(psets, key = lambda v: v[1]["Name"]):
for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Object")
# TODO reimplement. See #1222.
@@ -163,10 +172,12 @@ class BIM_PT_object_qtos(Panel):
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
row.operator("bim.add_qto", icon="ADD", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
op.obj = context.active_object.name
op.obj_type = "Object"
qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]]
for qto_id, qto in sorted(qtos, key = lambda v: v[1]["Name"]):
for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Object")
@@ -202,11 +213,111 @@ class BIM_PT_material_psets(Panel):
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "material_pset_name", text="")
row.prop(props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.active_material.name
op.obj_type = "Material"
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
for pset_id, pset in sorted(psets, key = lambda v: v[1]["Name"]):
for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Material")
class BIM_PT_task_qtos(Panel):
bl_label = "IFC Task Quantity Sets"
bl_idname = "BIM_PT_task_qtos"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_work_schedules"
@classmethod
def poll(cls, context):
props = context.scene.BIMWorkScheduleProperties
total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
if total_tasks > 0 and props.active_task_index < total_tasks:
return True
return False
def draw(self, context):
props = context.scene.TaskPsetProperties
wprops = context.scene.BIMWorkScheduleProperties
tprops = context.scene.BIMTaskTreeProperties
ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id
if ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
op.obj_type = "Task"
qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]]
for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Task")
class BIM_PT_resource_qtos(Panel):
bl_label = "IFC Resource Quantity Sets"
bl_idname = "BIM_PT_resource_qtos"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_resources"
@classmethod
def poll(cls, context):
props = context.scene.BIMResourceProperties
total_resources = len(context.scene.BIMResourceTreeProperties.resources)
if total_resources > 0 and props.active_resource_index < total_resources:
return True
return False
def draw(self, context):
props = context.scene.ResourcePsetProperties
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
if ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
op.obj_type = "Resource"
qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]]
for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Resource")
class BIM_PT_profile_psets(Panel):
bl_label = "IFC Profile Property Sets"
bl_idname = "BIM_PT_profile_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_profiles"
@classmethod
def poll(cls, context):
props = context.scene.BIMProfileProperties
if not props.is_editing:
return False
total_profiles = len(context.scene.BIMProfileProperties.profiles)
if total_profiles > 0 and props.active_profile_index < total_profiles:
return True
return False
def draw(self, context):
props = context.scene.ProfilePsetProperties
pprops = context.scene.BIMProfileProperties
ifc_definition_id = pprops.profiles[pprops.active_profile_index].ifc_definition_id
if ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj_type = "Profile"
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]]
for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Profile")
@@ -344,8 +344,14 @@ class EditResourceTime(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name =="LevelingDelay" or "Work" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -189,6 +189,11 @@ class AssignClass(bpy.types.Operator):
continue
spatial_obj = bpy.data.objects.get(collection.name)
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id)
if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"):
continue
elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
continue
bpy.ops.bim.assign_container(
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
)
@@ -76,9 +76,7 @@ classes = (
operator.AddTaskColumn,
operator.RemoveTaskColumn,
operator.SetTaskSortColumn,
operator.EnableAssigningResources,
operator.AssignResource,
operator.UnassignResource,
operator.EnableAssigningProcessToResource,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.Task,
@@ -58,9 +58,15 @@ class EditWorkPlan(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -214,9 +220,15 @@ class EditWorkSchedule(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -564,9 +576,15 @@ class EditTaskTime(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "ScheduleDuration":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -800,22 +818,44 @@ class AssignProcess(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
parent_resource: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
self.file = IfcStore.get_file()
if self.parent_resource:
resource = ifcopenshell.api.run(
"resource.add_resource",
self.file,
**{
"parent_resource": self.file.by_id(self.parent_resource),
"ifc_class": self.file.by_id(self.parent_resource).is_a(),
"name": self.file.by_id(self.parent_resource).Name + ": " + self.file.by_id(self.task).Name,
},
)
ifcopenshell.api.run(
"sequence.assign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_process=self.file.by_id(self.task),
**{
"related_object": resource,
"relating_process": self.file.by_id(self.task),
},
)
ResourceData.load(self.file)
bpy.ops.bim.load_resources()
else:
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
ifcopenshell.api.run(
"sequence.assign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_process=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
@@ -826,22 +866,38 @@ class UnassignProcess(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
resource: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
self.file = IfcStore.get_file()
if self.resource:
ifcopenshell.api.run(
"sequence.unassign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
related_object=self.file.by_id(self.resource),
relating_process=self.file.by_id(self.task),
)
ifcopenshell.api.run(
"resource.remove_resource",
self.file,
resource=self.file.by_id(self.resource),
)
ResourceData.load(self.file)
bpy.ops.bim.load_resources()
else:
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
ifcopenshell.api.run(
"sequence.unassign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_process=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
@@ -1962,8 +2018,8 @@ class SetTaskSortColumn(bpy.types.Operator):
return {"FINISHED"}
class EnableAssigningResources(bpy.types.Operator):
bl_idname = "bim.enable_assigning_resources"
class EnableAssigningProcessToResource(bpy.types.Operator):
bl_idname = "bim.enable_assigning_process_to_resources"
bl_label = "Enable Assigning Resources To Tasks"
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
@@ -1973,66 +2029,3 @@ class EnableAssigningResources(bpy.types.Operator):
self.props.active_task_id = self.task
self.props.editing_task_type = "RESOURCES"
return {"FINISHED"}
class AssignResource(bpy.types.Operator):
bl_idname = "bim.assign_resource"
bl_label = "Assign Resource"
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
parent_resource: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
resource = ifcopenshell.api.run(
"resource.add_resource",
self.file,
**{
"parent_resource": self.file.by_id(self.parent_resource),
"ifc_class": self.file.by_id(self.parent_resource).is_a(),
"name": self.file.by_id(self.parent_resource).Name + ": " + self.file.by_id(self.task).Name,
},
)
ifcopenshell.api.run(
"sequence.assign_process",
self.file,
**{
"related_object": resource,
"relating_process": self.file.by_id(self.task),
},
)
Data.load(self.file)
ResourceData.load(self.file)
bpy.ops.bim.load_resources()
return {"FINISHED"}
class UnassignResource(bpy.types.Operator):
bl_idname = "bim.unassign_resource"
bl_label = "Unassign Resource"
bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
resource: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_process",
self.file,
related_object=self.file.by_id(self.resource),
relating_process=self.file.by_id(self.task),
)
ifcopenshell.api.run(
"resource.remove_resource",
self.file,
resource=self.file.by_id(self.resource),
)
Data.load(self.file)
ResourceData.load(self.file)
bpy.ops.bim.load_resources()
return {"FINISHED"}
@@ -293,20 +293,19 @@ class BIM_PT_work_schedules(Panel):
def draw_editable_task_resource_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "resources", text="")
op = row.operator("bim.assign_resource", text="", icon="ADD")
op.parent_resource = int(self.props.resources)
op.task = self.props.active_task_id
task = Data.tasks[self.props.active_task_id]
ResourceData.load(IfcStore.get_file())
for related_obect_id in task["OperatesOn"]:
resource = ResourceData.resources[related_obect_id]
row = self.layout.row(align=True)
row.label(text=resource["Name"], icon="COMMUNITY")
op = row.operator("bim.unassign_resource", text="", icon="X")
op = row.operator("bim.assign_process", text="", icon="ADD")
if self.props.resources:
op.parent_resource = int(self.props.resources)
op.task = self.props.active_task_id
op.resource = related_obect_id
task = Data.tasks[self.props.active_task_id]
ResourceData.load(IfcStore.get_file())
for related_obect_id in task["OperatesOn"]:
resource = ResourceData.resources[related_obect_id]
row = self.layout.row(align=True)
row.label(text=resource["Name"], icon="COMMUNITY")
op = row.operator("bim.unassign_process", text="", icon="X")
op.task = self.props.active_task_id
op.resource = related_obect_id
class BIM_UL_task_columns(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -436,7 +435,7 @@ class BIM_UL_tasks(UIList):
row.operator(
"bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO"
).task = item.ifc_definition_id
row.operator("bim.enable_assigning_resources", text="", icon="COMMUNITY").task = item.ifc_definition_id
row.operator("bim.enable_assigning_process_to_resources", text="", icon="COMMUNITY").task = item.ifc_definition_id
row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id
row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
@@ -8,6 +8,7 @@ classes = (
operator.DisableEditingType,
operator.SelectSimilarType,
operator.SelectTypeObjects,
operator.SelectType,
prop.BIMTypeProperties,
prop.BIMTypeObjectProperties,
ui.BIM_PT_type,
@@ -109,6 +109,24 @@ class DisableEditingType(bpy.types.Operator):
return {"FINISHED"}
class SelectType(bpy.types.Operator):
bl_idname = "bim.select_type"
bl_label = "Select Type"
bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
related_object = bpy.data.objects.get(self.related_object) if self.related_object else context.active_object
oprops = related_object.BIMObjectProperties
obj = IfcStore.get_element(
ifcopenshell.util.element.get_type(self.file.by_id(oprops.ifc_definition_id)).GlobalId
)
context.view_layer.objects.active = obj
obj.select_set(True)
return {"FINISHED"}
class SelectSimilarType(bpy.types.Operator):
bl_idname = "bim.select_similar_type"
bl_label = "Select Similar Type"
@@ -64,6 +64,7 @@ class BIM_PT_type(Panel):
else:
label = name
row.label(text=label)
row.operator("bim.select_type", icon="TRACKER", text="")
row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
if name != "None/None":
@@ -3,10 +3,12 @@ from . import ui, prop, operator
classes = (
operator.AssignUnit,
operator.UnassignUnit,
operator.LoadUnits,
operator.DisableUnitEditingUI,
operator.RemoveUnit,
operator.AddMonetaryUnit,
operator.AddSIUnit,
operator.EnableEditingUnit,
operator.DisableEditingUnit,
operator.EditUnit,
@@ -1,5 +1,6 @@
import bpy
import ifcopenshell.api
import ifcopenshell.util.unit
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.unit.data import Data
@@ -9,13 +10,20 @@ class AssignUnit(bpy.types.Operator):
bl_idname = "bim.assign_unit"
bl_label = "Assign Unit"
bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifcopenshell.api.run("unit.assign_unit", IfcStore.get_file(), **self.get_units(context))
Data.load(IfcStore.get_file())
self.file = IfcStore.get_file()
if self.unit:
ifcopenshell.api.run("unit.assign_unit", self.file, units=[self.file.by_id(self.unit)])
else:
ifcopenshell.api.run("unit.assign_unit", self.file, **self.get_units(context))
Data.load(self.file)
if self.unit:
bpy.ops.bim.load_units()
return {"FINISHED"}
def get_units(self, context):
@@ -46,6 +54,23 @@ class AssignUnit(bpy.types.Operator):
return units
class UnassignUnit(bpy.types.Operator):
bl_idname = "bim.unassign_unit"
bl_label = "Unassign Unit"
bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("unit.unassign_unit", IfcStore.get_file(), units=[self.file.by_id(self.unit)])
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class LoadUnits(bpy.types.Operator):
bl_idname = "bim.load_units"
bl_label = "Load Units"
@@ -56,8 +81,7 @@ class LoadUnits(bpy.types.Operator):
while len(props.units) > 0:
props.units.remove(0)
for ifc_definition_id in Data.unit_assignment:
unit = Data.units[ifc_definition_id]
for ifc_definition_id, unit in Data.units.items():
name = unit.get("Name", "")
if unit["type"] == "IfcMonetaryUnit":
@@ -86,6 +110,7 @@ class LoadUnits(bpy.types.Operator):
new.ifc_definition_id = ifc_definition_id
new.name = name
new.unit_type = unit_type
new.is_assigned = ifc_definition_id in Data.unit_assignment
new.icon = icon
props.is_editing = True
@@ -132,8 +157,29 @@ class AddMonetaryUnit(bpy.types.Operator):
def _execute(self, context):
props = context.scene.BIMUnitProperties
self.file = IfcStore.get_file()
unit = ifcopenshell.api.run("unit.add_monetary_unit", self.file)
ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit])
ifcopenshell.api.run("unit.add_monetary_unit", self.file)
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class AddSIUnit(bpy.types.Operator):
bl_idname = "bim.add_si_unit"
bl_label = "Add SI Unit"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMUnitProperties
self.file = IfcStore.get_file()
unit = ifcopenshell.api.run(
"unit.add_si_unit",
self.file,
unit_type=props.named_unit_types,
name=ifcopenshell.util.unit.si_type_names[props.named_unit_types],
)
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
@@ -1,6 +1,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -17,11 +18,14 @@ from bpy.props import (
unitclasses_enum = []
namedunittypes_enum = []
def purge():
global unitclasses_enum
global namedunittypes_enum
unitclasses_enum = []
namedunittypes_enum = []
def getUnitClasses(self, context):
@@ -33,9 +37,20 @@ def getUnitClasses(self, context):
return unitclasses_enum
def getNamedUnitTypes(self, context):
global namedunittypes_enum
if not len(namedunittypes_enum) and IfcStore.get_file():
values = ifcopenshell.util.attribute.get_enum_items(
IfcStore.get_schema().declaration_by_name("IfcNamedUnit").all_attributes()[1]
)
namedunittypes_enum.extend([(c, c, "") for c in sorted(values)])
return namedunittypes_enum
class Unit(PropertyGroup):
name: StringProperty(name="Name")
unit_type: StringProperty(name="Unit Type")
is_assigned: BoolProperty(name="Is Assigned")
icon: StringProperty(name="Icon")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -46,4 +61,5 @@ class BIMUnitProperties(PropertyGroup):
active_unit_index: IntProperty(name="Active Unit Index")
active_unit_id: IntProperty(name="Active Unit Id")
unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes")
named_unit_types: EnumProperty(items=getNamedUnitTypes, name="Named Unit Types")
unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute)
@@ -40,8 +40,11 @@ class BIM_PT_units(Panel):
row.operator("bim.add_monetary_unit", text="", icon="ADD")
elif self.props.unit_classes == "IfcDerivedUnit":
pass # TODO
elif self.props.unit_classes == "IfcSIUnit":
row.prop(self.props, "named_unit_types", text="")
row.operator("bim.add_si_unit", text="", icon="ADD")
else:
pass # TODO
row.prop(self.props, "named_unit_types", text="")
self.layout.template_list(
"BIM_UL_units",
@@ -67,6 +70,13 @@ class BIM_UL_units(UIList):
row.label(text=item.unit_type or "No Type", icon=item.icon)
row.label(text=item.name or "Unnamed")
if item.is_assigned:
op = row.operator("bim.unassign_unit", text="", icon="KEYFRAME_HLT", emboss=False)
op.unit = item.ifc_definition_id
else:
op = row.operator("bim.assign_unit", text="", icon="KEYFRAME", emboss=False)
op.unit = item.ifc_definition_id
if props.active_unit_id == item.ifc_definition_id:
row.operator("bim.edit_unit", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_unit", text="", icon="CANCEL")
+1 -1
View File
@@ -30,7 +30,7 @@ class IfcSchema:
self.property_files = []
property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4")
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
for path in property_paths:
self.psetqto.templates.append(ifcopenshell.open(path))
+2
View File
@@ -58,6 +58,7 @@ class LibraryGenerator:
profile = self.file.create_entity(
"IfcIShapeProfileDef",
ProfileName="DEMO-I",
ProfileType="AREA",
OverallWidth=0.1,
OverallDepth=0.2,
@@ -69,6 +70,7 @@ class LibraryGenerator:
profile = self.file.create_entity(
"IfcCShapeProfileDef",
ProfileName="DEMO-C",
ProfileType="AREA",
Depth=0.2,
Width=0.1,
+27
View File
@@ -0,0 +1,27 @@
# Server-Test
```
$ pip install -r requirements.txt
$ cd bcfserver/
$ python
>>> from run import db
>>> db.create_all()
$ export FLASK_APP=run.py
$ flask run
```
Go to [http://localhost:5000](http://localhost:5000) to see the server
# Register the user
1. Go to http://localhost:5000/register to register the user
2. Create the client
3. For grant type enter authorization_code
4. For response_type enter code secret
5. Enter the scope and create the client
### You will be redirected to the page with the details of your client id and secret
# Foundation API
- Set the Base URL will be `http://127.0.0.1:5000/`
@@ -0,0 +1,71 @@
{
"Projects":[{
"project_id": "F445F4F2-4D02-4B2A-B612-5E456BEF9137",
"name": "Example project 1",
"authorization": {
"project_actions": [
"createTopic",
"createDocument"
]
}
},
{
"project_id": "A233FBB2-3A3B-EFF4-C123-DE22ABC8414",
"name": "Example project 2",
"authorization": {
"project_actions": []
}
}],
"Extensions":[{
"topic_type": [
"Information",
"Error"
],
"topic_status": [
"Open",
"Closed",
"ReOpened"
],
"topic_label": [
"Architecture",
"Structural",
"MEP"
],
"snippet_type": [
".ifc",
".csv"
],
"priority": [
"Low",
"Medium",
"High"
],
"users": [
"Architect@example.com",
"BIM-Manager@example.com",
"bob_heater@example.com"
],
"stage": [
"Preliminary Planning End",
"Construction Start",
"Construction End"
],
"project_actions": [
"update",
"createTopic",
"createDocument"
],
"topic_actions": [
"update",
"updateBimSnippet",
"updateRelatedTopics",
"updateDocumentReferences",
"updateFiles",
"createComment",
"createViewpoint"
],
"comment_actions": [
"update"
]
}]
}
@@ -0,0 +1,700 @@
from operator import methodcaller
from flask import jsonify, url_for, redirect, render_template, request, session, flash
from flask_login import login_user, logout_user, login_required, current_user
from flask.blueprints import Blueprint
from foundation.models import User, OAuth2AuthorizationCode, OAuth2Token, OAuth2Client
from run import app, db
import json
import os
bcf = Blueprint("bcf", __name__, template_folder="templates", url_prefix="/bcf/3.0")
my_absolute_dirpath = os.path.abspath(os.path.dirname(__file__))
data = open(
f"{my_absolute_dirpath}/project.json",
)
jdata = json.load(data)
def validate_client(request):
Headers = str.split(request.headers["Authorization"])
token = Headers[1]
access_token = OAuth2Token.query.filter_by(access_token=token).first()
if access_token:
return True
else:
return False
def invalid_user():
response = jsonify({"error": "User not recognized"})
response.status = 401
return response
def invalid_project():
response = jsonify({"message": "Project not found"})
response.status = 404
return response
@bcf.route("/projects")
def projects():
return invalid_user()
if validate_client(request):
return jsonify(jdata["Projects"])
return invalid_user()
@bcf.route("/")
@login_required
def bcf_3():
return "<h1>BCF HOMPAGE</h1>"
@bcf.route("/projects/<project_id>")
def project_details(project_id):
if validate_client(request):
for project in jdata["Projects"]:
if project["project_id"] == project_id:
return jsonify(project)
return invalid_project()
return invalid_user()
@bcf.route("/projects/<project_id>", methods=["PUT"])
def update_project(project_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/extensions")
def extensions(project_id):
if validate_client(request):
for project in jdata["Projects"]:
if project["project_id"] == project_id:
return jsonify(jdata["Extensions"])
return invalid_project()
return invalid_user()
@bcf.route("/projects/<project_id>/topics")
def topics(project_id):
access_token = validate_client(request)
if access_token:
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
response = app.response_class(
response=jdata["Topics"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["POST"])
def create_topic(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "POST":
body = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=body,
status=201,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>")
def topic_details(project_id, topic_id):
access_token = validate_client(request)
if access_token:
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=j,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["PUT"])
def update_topic(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["DELETE"])
def delete_topic(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "DELETE":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
message = {"DELETED"}
response = app.response_class(
response=json.dumps(message),
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/snippet", methods=["GET"])
def get_snippet(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=json.dumps("Snippet test successfull"),
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/snippet", methods=["PUT"])
def update_snippet(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/files_information", methods=["GET"])
def get_files_information(project_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
response = app.response_class(
response=jdata["Files"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/files", methods=["GET"])
def get_files(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=json.dumps("Get request for files successfull"),
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/files", methods=["PUT"])
def update_files(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments", methods=["GET"])
def get_comments(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=jdata["Comments"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments", methods=["POST"])
def create_comments(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "POST":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["GET"]
)
def get_comment(project_id, topic_id, comment_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["Comments"]:
if (k["guid"]) == comment_id:
response = app.response_class(
response=k,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["PUT"]
)
def update_comment(project_id, topic_id, comment_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["Comments"]:
if (k["guid"]) == comment_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["DELETE"]
)
def delete_comment(project_id, topic_id, comment_id):
access_token = validate_client(request)
if access_token and request.method == "DELETE":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["Comments"]:
if (k["guid"]) == comment_id:
message = {"DELETED"}
response = app.response_class(
response=json.dumps(message),
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
"""
***Add ViewPoints Routes Here ***
"""
@bcf.route("/projects/<project_id>/topics/<topic_id>/related_topics", methods=["GET"])
def get_related_topics(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=jdata["RelatedTopics"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/related_topics", methods=["PUT"])
def update_related_topics(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/document_references", methods=["GET"]
)
def get_document_references(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=jdata["DocumentReferences"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/document_references", methods=["POST"]
)
def create_document_references(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "POST":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/document_references/<document_reference_id>",
methods=["PUT"],
)
def update_document_references(project_id, topic_id, document_reference_id):
access_token = validate_client(request)
if access_token and request.method == "PUT":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["DocumentReferences"]:
if (k["guid"]) == document_reference_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/documents", methods=["GET"])
def get_documents(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=jdata["Documents"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/documents", methods=["POST"])
def create_documents(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "POST":
data = request.form["data"]
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
response = app.response_class(
response=data,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/documents/<document_id>", methods=["GET"]
)
def get_document(project_id, topic_id, document_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["Documents"]:
if (k["guid"]) == document_id:
response = app.response_class(
response=k,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/events", methods=["GET"])
def get_events(project_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
response = app.response_class(
response=jdata["Events"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/<topic_id>/events", methods=["GET"])
def get_topic_events(project_id, topic_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["Events"]:
if (k["topic_guid"]) == topic_id:
response = app.response_class(
response=k,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route("/projects/<project_id>/topics/comments/events", methods=["GET"])
def get_comments_events(project_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
response = app.response_class(
response=jdata["EventComments"],
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@bcf.route(
"/projects/<project_id>/topics/<topic_id>/comments/<comment_id>/events",
methods=["GET"],
)
def get_comment_events(project_id, topic_id, comment_id):
access_token = validate_client(request)
if access_token and request.method == "GET":
for i in jdata["Projects"]:
if (i["project_id"]) == project_id:
for j in jdata["Topics"]:
if (j["guid"]) == topic_id:
for k in jdata["EventComments"]:
if (k["comment_guid"]) == comment_id:
response = app.response_class(
response=k,
status=200,
mimetype="application/json",
)
return response
response = invalid_project()
return response
else:
response = invalid_user()
return response
@@ -11,8 +11,8 @@ import urllib
import json
from run import db, app
website_obj = Blueprint(
"website_obj",
foundation_obj = Blueprint(
"foundation_obj",
__name__,
template_folder="templates",
)
@@ -22,13 +22,13 @@ def split_by_crlf(s):
return [v for v in s.splitlines() if v]
@website_obj.route("/")
@foundation_obj.route("/")
def homepage():
return render_template("index.html")
# return "homepage"
@website_obj.route("/register", methods=["GET", "POST"])
@foundation_obj.route("/register", methods=["GET", "POST"])
def register_page():
form = RegisterForm()
if form.validate_on_submit():
@@ -45,7 +45,7 @@ def register_page():
f"Account created successfully! {user_to_create.username}",
category="success",
)
return redirect(url_for("website_obj.homepage"))
return redirect(url_for("foundation_obj.homepage"))
if form.errors != {}:
for err_msg in form.errors.values():
flash(
@@ -55,7 +55,7 @@ def register_page():
return render_template("register.html", form=form)
@website_obj.route("/createclient", methods=["GET", "POST"])
@foundation_obj.route("/createclient", methods=["GET", "POST"])
@login_required
def create_client():
grants = [
@@ -99,7 +99,7 @@ def create_client():
return render_template("createclient.html", form=form, grants=grants)
@website_obj.route("/login", methods=["GET", "POST"])
@foundation_obj.route("/login", methods=["GET", "POST"])
def login_page():
form = LoginForm()
if form.validate_on_submit():
@@ -108,7 +108,7 @@ def login_page():
attempted_password=form.password.data
):
login_user(attempted_user)
return redirect(url_for("website_obj.homepage"))
return redirect(url_for("foundation_obj.homepage"))
else:
flash(
"Invalid Credentials",
@@ -118,14 +118,14 @@ def login_page():
return render_template("login.html", form=form)
@website_obj.route("/logout")
@foundation_obj.route("/logout")
def logoutpage():
logout_user()
flash("You have been logged out!", category="info")
return redirect(url_for("website_obj.homepage"))
return redirect(url_for("foundation_obj.homepage"))
@website_obj.route("/foundation/1.0/auth")
@foundation_obj.route("/foundation/1.0/auth")
def foundation_auth():
data = {
"oauth2_auth_url": "http://127.0.0.1:5000/oauth/authorize",
@@ -138,7 +138,7 @@ def foundation_auth():
return response
@website_obj.route("/foundation/versions")
@foundation_obj.route("/foundation/versions")
def foundation_versions():
Body = {
"versions": [
@@ -167,7 +167,7 @@ def foundation_versions():
return response
@website_obj.route("/outh/login", methods=["GET", "POST"])
@foundation_obj.route("/outh/login", methods=["GET", "POST"])
def oauth_login():
form = LoginForm()
if form.validate_on_submit():
@@ -185,7 +185,7 @@ def oauth_login():
state = request.args.get("state")
return redirect(
url_for(
"website_obj.authorize",
"foundation_obj.authorize",
client_id=client_id,
redirect_uri=redirect_uri,
state=state,
@@ -199,7 +199,7 @@ def oauth_login():
return render_template("ologin.html", form=form)
@website_obj.route("/oauth/authorize", methods=["GET", "POST"])
@foundation_obj.route("/oauth/authorize", methods=["GET", "POST"])
def authorize():
client_id = request.args.get("client_id")
redirect_uri = request.args.get("redirect_uri")
@@ -209,7 +209,7 @@ def authorize():
query = request.query_string
return redirect(
url_for(
"website_obj.oauth_login",
"foundation_obj.oauth_login",
client_id=client_id,
redirect_uri=redirect_uri,
state=state,
@@ -252,7 +252,7 @@ def authorize():
return render_template("oauth.html")
@website_obj.route("/oauth/token", methods=["POST"])
@foundation_obj.route("/oauth/token", methods=["POST"])
def issue_token():
try:
code = request.form["code"]
@@ -30,7 +30,7 @@
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('website_obj.homepage') }}"
<a class="nav-link" href="{{ url_for('foundation_obj.homepage') }}"
>Home <span class="sr-only">(current)</span></a
>
</li>
@@ -47,14 +47,16 @@
<a class="nav-link">Welcome, {{ current_user.username }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('website_obj.logoutpage') }}"
<a
class="nav-link"
href="{{ url_for('foundation_obj.logoutpage') }}"
>Logout</a
>
</li>
<li class="nav-item">
<a
class="nav-link"
href="{{ url_for('website_obj.create_client') }}"
href="{{ url_for('foundation_obj.create_client') }}"
>Create Client</a
>
</li>
@@ -62,14 +64,16 @@
{% else %}
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('website_obj.login_page') }}"
<a
class="nav-link"
href="{{ url_for('foundation_obj.login_page') }}"
>Login</a
>
</li>
<li class="nav-item">
<a
class="nav-link"
href="{{ url_for('website_obj.register_page') }}"
href="{{ url_for('foundation_obj.register_page') }}"
>Register</a
>
</li>
@@ -15,7 +15,7 @@
<h6>Do not have an account?</h6>
<a
class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.register_page') }}"
href="{{ url_for('foundation_obj.register_page') }}"
>Register</a
>
</div>
@@ -16,7 +16,7 @@
<h6>Do not have an account?</h6>
<a
class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.register_page') }}"
href="{{ url_for('foundation_obj.register_page') }}"
>Register</a
>
</div>
@@ -18,7 +18,7 @@
<h6>Already have an account?</h6>
<a
class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.login_page') }}"
href="{{ url_for('foundation_obj.login_page') }}"
>Login</a
>
</div>
@@ -10,14 +10,14 @@ login_manager = LoginManager(app)
bcrypt = Bcrypt(app)
app.config["SECRET_KEY"] = "f613729206685405cde0e388"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
login_manager.login_view = "website_obj.login_page"
login_manager.login_view = "foundation_obj.login_page"
login_manager.login_message_category = "info"
from website.routes import website_obj
from foundation.routes import foundation_obj
from bcf.routes import bcf
app.register_blueprint(website_obj)
app.register_blueprint(foundation_obj)
app.register_blueprint(bcf)
+4 -1
View File
@@ -60,18 +60,20 @@ class Csv2Ifc:
def get_row_cost_data(self, row):
name = row[self.headers["Name"]]
identification = row[self.headers["Identification"]] if "Identification" in self.headers else None
cost_quantities = row[self.headers["Quantity"]]
cost_quantities_unit = row[self.headers["Unit"]]
if self.has_categories:
cost_values = {
k: float(row[v])
for k, v in self.headers.items()
if k not in ["Hierarchy", "Name", "Quantity", "Unit", "Subtotal"] and row[v]
if k not in ["Hierarchy", "Identification", "Name", "Quantity", "Unit", "Subtotal"] and row[v]
}
else:
cost_values = row[self.headers["Value"]]
cost_values = float(cost_values) if cost_values else None
return {
"Identification": str(identification) if identification else None,
"Name": str(name) if name else None,
"CostQuantities": float(cost_quantities) if cost_quantities else None,
"CostQuantitiesUnit": str(cost_quantities_unit) if cost_quantities_unit else None,
@@ -99,6 +101,7 @@ class Csv2Ifc:
cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent)
cost_item["ifc"].Name = cost_item["Name"]
cost_item["ifc"].Identification = cost_item["Identification"]
if not cost_item["CostValues"]:
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"])
+14 -14
View File
@@ -1,14 +1,14 @@
"Hierarchy","Name","Quantity","Unit","Foo","Bar","Baz","Subtotal"
1,"Demolition",,,,,,301
,,,,,,,
2,"Building A",,,,,,96
,,,,,,,
3,"Soft strip",1,"m",5,4,3,12
3,"Hard Strip",2,"m2",6,5,4,30
3,"Hazmat",3,"m3",7,6,5,54
,,,,,,,
2,"Building B",,,,,,205
,,,,,,,
3,"Soft strip",4,"hr",8,7,,60
3,"Hard Strip",5,,9,,8,85
3,"Hazmat",6,"kg",10,,,60
"Hierarchy","Identification","Name","Quantity","Unit","Foo","Bar","Baz","Subtotal"
1,1,"Demolition",,,,,,301
,,,,,,,,
2,1.1,"Building A",,,,,,96
,,,,,,,,
3,"1.1.1","Soft strip",1,"m",5,4,3,12
3,"1.1.2","Hard Strip",2,"m2",6,5,4,30
3,"1.1.3","Hazmat",3,"m3",7,6,5,54
,,,,,,,,
2,1.2,"Building B",,,,,,205
,,,,,,,,
3,"1.2.1","Soft strip",4,"hr",8,7,,60
3,"1.2.2","Hard Strip",5,,9,,8,85
3,"1.2.3","Hazmat",6,"kg",10,,,60
1 Hierarchy Identification Name Quantity Unit Foo Bar Baz Subtotal
2 1 1 Demolition 301
3
4 2 1.1 Building A 96
5
6 3 1.1.1 Soft strip 1 m 5 4 3 12
7 3 1.1.2 Hard Strip 2 m2 6 5 4 30
8 3 1.1.3 Hazmat 3 m3 7 6 5 54
9
10 2 1.2 Building B 205
11
12 3 1.2.1 Soft strip 4 hr 8 7 60
13 3 1.2.2 Hard Strip 5 9 8 85
14 3 1.2.3 Hazmat 6 kg 10 60
+52 -30
View File
@@ -24,15 +24,20 @@ from datetime import datetime
JSON_TO_IFC = {
"Building": ["IfcBuilding"],
"BuildingPart": ["IfcBuilding", {"CompositionType": "Partial"}], # CompositionType: Partial
"BuildingInstallation": ["IfcDistributionElement"],
"Road": ["IfcCivilElement"],
"TransportSquare": ["IfcSpace"],
"TINRelief": ["IfcGeographicElement"],
"WaterBody": ["IfcGeographicElement"], # Update for IFC4.3
"LandUse": ["IfcGeographicElement"],
"PlantCover": ["IfcGeographicElement"],
"SolitaryVegetationObject": ["IfcGeographicElement"],
"BuildingPart": ["IfcBuilding", {"CompositionType": "PARTIAL"}],
"BuildingInstallation": ["IfcBuildingElementProxy"],
"Road": ["IfcCivilElement"], # Update for IFC4.3
"Railway": ["IfcCivilElement"], # Update for IFC4.3
"TransportSquare": ["IfcCivilElement"], # Update for IFC4.3
"TINRelief": ["IfcGeographicElement", {"PredefinedType": "TERRAIN"}],
"WaterBody": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "WaterBody"}], # Update for IFC4.3
"LandUse": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "LandUse"}],
"PlantCover": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "Plantcover"}],
"SolitaryVegetationObject": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "SolitaryVegetationObject"}],
"CityFurniture": ["IfcFurnishingElement"],
"GenericCityObject": ["IfcCivilElement"],
"Bridge": ["IfcCivilElement"], # Update for IFC4.3
@@ -42,7 +47,7 @@ JSON_TO_IFC = {
"Tunnel": ["IfcCivilElement"], # Update for IFC4.3
"TunnelPart": ["IfcCivilElement"], # Update for IFC4.3
"TunnelInstallation": ["IfcCivilElement"], # Update for IFC4.3
"CityObjectGroup": ["IfcCivilElement"],
"CityObjectGroup": ["IfcBuilding"], # Update for IFC4.3
"GroundSurface": ["IfcSlab", {"PredefinedType": "BASESLAB"}],
"RoofSurface": ["IfcRoof"],
"WallSurface": ["IfcWall"],
@@ -51,9 +56,12 @@ JSON_TO_IFC = {
"OuterFloorSurface": ["IfcSlab", {"PredefinedType": "FLOOR"}],
"Window": ["IfcWindow"],
"Door": ["IfcDoor"],
"WaterSurface": ["IfcGeographicElement"], # Update for IFC4.3
"WaterGroundSurface": ["IfcGeographicElement"], # Update for IFC4.3
"WaterClosureSurface": ["IfcGeographicElement"], # Update for IFC4.3
"WaterSurface": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "WaterSurface"}], # Update for IFC4.3
"WaterGroundSurface": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "WaterGroundSurface"}], # Update for IFC4.3
"WaterClosureSurface": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "WaterClosureSurface"}], # Update for IFC4.3
"TrafficArea": ["IfcCivilElement"], # Update for IFC4.3
"AuxiliaryTrafficArea": ["IfcCivilElement"] # Update for IFC4.3
}
@@ -137,6 +145,7 @@ class Cityjson2ifc:
self.IFC_model.write(self.properties["file_destination"])
def create_IFC_classes(self):
parents_children_relations = {"IfcSite": {'Parent': self.IFC_site, 'Children': []}}
for obj_id, obj in self.city_model.get_cityobjects().items():
# CityJSON type to class
@@ -153,22 +162,19 @@ class Cityjson2ifc:
if "name_attribute" in self.properties and self.properties["name_attribute"] in obj.attributes:
IFC_name = obj.attributes[self.properties["name_attribute"]]
# TODO children
# TODO parents
# TODO geometry_type
# geometry_lod
lod = 0
geometry = None
if len(obj.geometry) == 0:
print(f"Warning: Object {obj_id} has no geometry.")
for geom in obj.geometry:
if geom.lod > lod:
geometry = geom
lod = geom.lod
IFC_children = []
if geometry.surfaces:
IFC_semantic_surface_children = []
if geometry and geometry.surfaces:
for surface_id in geometry.surfaces:
IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0]
child_data = {"GlobalId": ifcopenshell.guid.new(),
@@ -179,9 +185,9 @@ class Cityjson2ifc:
surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id)
if surface_geometry:
child_data["Representation"] = self.create_IFC_representation(surface_geometry, 'brep')
IFC_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data))
IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data))
else:
elif geometry:
IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, geometry)
if IFC_geometry:
data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type)
@@ -189,20 +195,36 @@ class Cityjson2ifc:
data["Name"] = IFC_name
IFC_object = self.IFC_model.create_entity(IFC_class, **data)
# Define aggregation
self.IFC_model.create_entity("IfcRelContainedInSpatialStructure",
**{"GlobalId": ifcopenshell.guid.new(),
"RelatedElements": [IFC_object],
"RelatingStructure": self.IFC_site}
)
if IFC_children:
if len(obj.parents) == 0:
parents_children_relations["IfcSite"]['Children'].append(IFC_object)
for parent in obj.parents:
if parent not in parents_children_relations:
parents_children_relations[parent] = {'Parent': None, 'Children': []}
parents_children_relations[parent]['Children'].append(IFC_object)
if len(obj.children) > 0:
if obj_id not in parents_children_relations:
parents_children_relations[obj_id] = {'Parent': None, 'Children': []}
parents_children_relations[obj_id]['Parent'] = IFC_object
if IFC_semantic_surface_children:
self.IFC_model.create_entity("IfcRelAggregates",
**{"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": IFC_children,
"RelatedObjects": IFC_semantic_surface_children,
"RelatingObject": IFC_object})
self.create_property_set(obj.attributes, IFC_object)
for parent_children in parents_children_relations.values():
self.IFC_model.create_entity("IfcRelContainedInSpatialStructure",
**{"GlobalId": ifcopenshell.guid.new(),
"RelatedElements": parent_children['Children'],
"RelatingStructure": parent_children['Parent']}
)
def create_IFC_representation(self, IFC_geometry, shape_representation_type):
if not isinstance(IFC_geometry, list):
IFC_geometry = [IFC_geometry]
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -30,7 +30,11 @@ bool IfcGeom::Kernel::convert_shapes(const IfcBaseClass* l, IfcRepresentationSha
if (shape_type(l) != ST_SHAPELIST) {
TopoDS_Shape shp;
if (convert_shape(l, shp)) {
r.push_back(IfcGeom::IfcRepresentationShapeItem(l->data().id(), shp, get_style(l->as<IfcSchema::IfcRepresentationItem>())));
const IfcGeom::SurfaceStyle* style = nullptr;
if (l->as<IfcSchema::IfcRepresentationItem>()) {
style = get_style(l->as<IfcSchema::IfcRepresentationItem>());
}
r.push_back(IfcGeom::IfcRepresentationShapeItem(l->data().id(), shp, style));
return true;
}
return false;
@@ -1,31 +0,0 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "products": []}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
quantity_names = set()
for quantity in self.settings["cost_item"].CostQuantities or []:
if quantity.Name:
quantity_names.add(quantity.Name)
for product in self.settings["products"]:
ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=product,
relating_control=self.settings["cost_item"],
)
for name in quantity_names:
ifcopenshell.api.run(
"cost.assign_cost_item_product_quantities",
self.file,
cost_item=self.settings["cost_item"],
prop_name=name
)
@@ -4,25 +4,28 @@ import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "prop_name": ""}
self.settings = {"cost_item": None, "products": [], "prop_name": ""}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for control in self.settings["cost_item"].Controls or []:
for related_object in control.RelatedObjects:
self.add_quantity_from_related_object(related_object)
for product in self.settings["products"]:
ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=product,
relating_control=self.settings["cost_item"],
)
self.add_quantity_from_related_object(product)
self.settings["cost_item"].CostQuantities = list(self.quantities)
def add_quantity_from_related_object(self, element):
if element.is_a("IfcTypeObject"):
for definition in element.HasPropertySets or []:
self.add_quantity_from_qto(definition)
else:
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
if not element.is_a("IfcObject"):
return
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
def add_quantity_from_qto(self, qto):
if not qto.is_a("IfcElementQuantity"):
@@ -51,21 +51,43 @@ class Data:
del data["OwnerHistory"]
del data["CostValues"]
data["IsNestedBy"] = []
data["Controls"] = []
data["Controls"] = {}
for rel in cost_item.IsNestedBy:
[data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
parametric_quantities = []
for rel in cost_item.Controls:
[data["Controls"].append(o.id()) for o in rel.RelatedObjects or []]
for related_object in rel.RelatedObjects or []:
quantities = cls.get_object_quantities(cost_item, related_object)
data["Controls"][related_object.id()] = quantities
parametric_quantities.extend(quantities)
cls.cost_items[cost_item.id()] = data
cls.load_cost_item_quantities(cost_item, data)
cls.load_cost_item_quantities(cost_item, data, parametric_quantities)
cls.load_cost_item_values(cost_item, data)
cls.is_loaded = True
@classmethod
def load_cost_item_quantities(cls, cost_item, data):
def get_object_quantities(cls, cost_item, element):
if not element.is_a("IfcObject"):
return []
results = []
for relationship in element.IsDefinedBy:
if not relationship.is_a("IfcRelDefinesByProperties"):
continue
qto = relationship.RelatingPropertyDefinition
if not qto.is_a("IfcElementQuantity"):
continue
for prop in qto.Quantities:
if prop in cost_item.CostQuantities or []:
results.append(prop.id())
return results
@classmethod
def load_cost_item_quantities(cls, cost_item, data, parametric_quantities):
data["CostQuantities"] = []
data["TotalCostQuantity"] = cls.get_total_quantity(cost_item)
for quantity in cost_item.CostQuantities or []:
if quantity.id() in parametric_quantities:
continue
quantity_data = quantity.get_info()
del quantity_data["Unit"]
cls.physical_quantities[quantity.id()] = quantity_data
@@ -98,7 +120,11 @@ class Data:
def load_cost_item_value(cls, cost_item_data, cost_item, cost_value):
value_data = cost_value.get_info()
del value_data["AppliedValue"]
del value_data["UnitBasis"]
if value_data["UnitBasis"]:
data = cost_value.UnitBasis.get_info()
data["ValueComponent"] = data["ValueComponent"].wrappedValue
data["UnitComponent"] = data["UnitComponent"].id()
value_data["UnitBasis"] = data
if value_data["ApplicableDate"]:
value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
if value_data["FixedUntilDate"]:
@@ -1,3 +1,8 @@
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -10,4 +15,19 @@ class Usecase:
if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types
value = self.file.createIfcMonetaryMeasure(value)
elif name == "UnitBasis":
self.remove_existing_unit_basis()
if value:
value_component = self.file.create_entity(
ifcopenshell.util.unit.get_unit_measure_type(value["UnitComponent"].UnitType),
value["ValueComponent"],
)
value = self.file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
setattr(self.settings["cost_value"], name, value)
def remove_existing_unit_basis(self):
if (
self.settings["cost_value"].UnitBasis
and len(self.file.get_inverse(self.settings["cost_value"].UnitBasis)) == 1
):
ifcopenshell.util.element.remove_deep(self.file, self.settings["cost_value"].UnitBasis)
@@ -9,7 +9,7 @@ class Usecase:
layers = list(self.settings["layer_set"].MaterialLayers or [])
layer = self.file.create_entity("IfcMaterialLayer", **{
"Material": self.settings["material"],
"LayerThickness": 0.
"LayerThickness": 1.
})
layers.append(layer)
self.settings["layer_set"].MaterialLayers = layers
@@ -11,6 +11,7 @@ class Usecase():
self.settings[key] = value
def execute(self):
# TODO: don't also edit the profile def in this usecase
for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value)
self.settings["profile"].Material = self.settings["material"]
@@ -7,3 +7,4 @@ class Usecase:
def execute(self):
self.file.remove(self.settings["profile"])
# TODO: deep purge
@@ -0,0 +1,10 @@
class Usecase():
def __init__(self, file, **settings):
self.file = file
self.settings = {"profile": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"profile": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["profile"])
# TODO: deep purge
@@ -11,12 +11,15 @@ class Usecase:
def execute(self):
self.added_elements = set()
self.whitelisted_inverse_attributes = {}
if self.settings["element"].is_a("IfcTypeProduct"):
return self.append_type_product()
elif self.settings["element"].is_a("IfcMaterial"):
return self.append_material()
elif self.settings["element"].is_a("IfcCostSchedule"):
return self.append_cost_schedule()
elif self.settings["element"].is_a("IfcProfileDef"):
return self.append_profile_def()
def is_already_appended(self):
try:
@@ -36,6 +39,12 @@ class Usecase:
self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]}
return self.add_element(self.settings["element"])
def append_profile_def(self):
if [e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == self.settings["element"].ProfileName]:
return
self.whitelisted_inverse_attributes = {"IfcProfileDef": ["HasProperties"]}
return self.add_element(self.settings["element"])
def append_type_product(self):
if self.is_already_appended():
return
@@ -64,3 +64,15 @@ class Usecase:
"Material": self.settings["product"],
}
)
elif self.settings["product"].is_a("IfcProfileDef"):
for definition in self.settings["product"].HasProperties or []:
if definition.Name == self.settings["name"]:
return definition
return self.file.create_entity(
"IfcProfileProperties",
**{
"Name": self.settings["name"],
"ProfileDefinition": self.settings["product"],
}
)
@@ -26,6 +26,8 @@ class Data:
cls.add_type_product_psets(product, product_id)
elif product.is_a("IfcMaterialDefinition"):
cls.add_material_psets(product, product_id)
elif product.is_a("IfcProfileDef"):
cls.add_profile_psets(product, product_id)
else:
cls.add_product_psets(product, product_id)
@@ -44,6 +46,13 @@ class Data:
for pset in product.HasProperties:
cls.add_pset(pset, product_id)
@classmethod
def add_profile_psets(cls, product, product_id):
if not product.HasProperties:
return
for pset in product.HasProperties:
cls.add_pset(pset, product_id)
@classmethod
def add_product_psets(cls, product, product_id):
if not hasattr(product, "IsDefinedBy") or not product.IsDefinedBy:
@@ -59,7 +68,7 @@ class Data:
@classmethod
def add_pset(cls, pset, product_id):
data = pset.get_info()
if not pset.is_a("IfcMaterialProperties"):
if not pset.is_a("IfcMaterialProperties") and not pset.is_a("IfcProfileProperties"):
del data["OwnerHistory"]
del data["HasProperties"]
if hasattr(pset, "HasProperties"):
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit_type": "LENGTHUNIT", "name": "METRE"}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcSIUnit", UnitType=self.settings["unit_type"], Name=self.settings["name"])
@@ -24,6 +24,9 @@ class Data:
return
for unit in unit_assignment[0].Units:
cls.unit_assignment.append(unit.id())
for unit in (
cls.file.by_type("IfcDerivedUnit") + cls.file.by_type("IfcNamedUnit") + cls.file.by_type("IfcMonetaryUnit")
):
cls.load_unit(unit)
cls.is_loaded = True
@@ -0,0 +1,17 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"units": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
unit_assignment = self.file.by_type("IfcUnitAssignment")
if not unit_assignment:
return
unit_assignment = unit_assignment[0]
units = set(unit_assignment.Units or [])
units = units - set(self.settings["units"])
if units:
unit_assignment.Units = list(units)
return unit_assignment
+10 -2
View File
@@ -359,19 +359,27 @@ class file(object):
return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)]
return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(type)]
def traverse(self, inst, max_levels=None):
def traverse(self, inst, max_levels=None, breadth_first=False):
"""Get a list of all referenced instances for a particular instance including itself
:param inst: The entity instance to get all sub instances
:type inst: ifcopenshell.entity_instance.entity_instance
:param max_levels: How far deep to recursively fetch sub instances. None or -1 means infinite.
:type max_levels: None|int
:param breadth_first: Whether to use breadth-first search, the default is depth-first.
:type max_levels: bool
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
:rtype: list
"""
if max_levels is None:
max_levels = -1
return [entity_instance(e, self) for e in self.wrapped_data.traverse(inst.wrapped_data, max_levels)]
if breadth_first:
fn = self.wrapped_data.traverse_breadth_first
else:
fn = self.wrapped_data.traverse
return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)]
def get_inverse(self, inst):
"""Return a list of entities that reference this entity
@@ -104,7 +104,7 @@ def has_element_reference(value, element):
def remove_deep(ifc_file, element):
# @todo maybe some sort of try-finally mechanism.
ifc_file.batch()
subgraph = list(ifc_file.traverse(element))
subgraph = list(ifc_file.traverse(element, breadth_first=True))
subgraph_set = set(subgraph)
for ref in subgraph[::-1]:
if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0:
@@ -25,7 +25,7 @@ unit_names = [
"CANDELA",
"COULOMB",
"CUBIC_METRE",
"DEGREE CELSIUS",
"DEGREE_CELSIUS",
"FARAD",
"GRAM",
"GRAY",
@@ -43,7 +43,7 @@ unit_names = [
"SECOND",
"SIEMENS",
"SIEVERT",
"SQUARE METRE",
"SQUARE_METRE",
"METRE",
"STERADIAN",
"TESLA",
@@ -52,7 +52,6 @@ unit_names = [
"WEBER",
]
si_dimensions = {
"METRE": (1, 0, 0, 0, 0, 0, 0),
"SQUARE_METRE": (2, 0, 0, 0, 0, 0, 0),
@@ -87,6 +86,72 @@ si_dimensions = {
"OTHERWISE": (0, 0, 0, 0, 0, 0, 0),
}
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/72
si_type_names = {
"ABSORBEDDOSEUNIT": "GRAY",
"AMOUNTOFSUBSTANCEUNIT": "MOLE",
"AREAUNIT": "SQUARE_METRE",
"DOSEEQUIVALENTUNIT": "SIEVERT",
"ELECTRICCAPACITANCEUNIT": "FARAD",
"ELECTRICCHARGEUNIT": "COULOMB",
"ELECTRICCONDUCTANCEUNIT": "SIEMENS",
"ELECTRICCURRENTUNIT": "AMPERE",
"ELECTRICRESISTANCEUNIT": "OHM",
"ELECTRICVOLTAGEUNIT": "VOLT",
"ENERGYUNIT": "JOULE",
"FORCEUNIT": "NEWTON",
"FREQUENCYUNIT": "HERTZ",
"ILLUMINANCEUNIT": "LUX",
"INDUCTANCEUNIT": "HENRY",
"LENGTHUNIT": "METRE",
"LUMINOUSFLUXUNIT": "LUMEN",
"LUMINOUSINTENSITYUNIT": "CANDELA",
"MAGNETICFLUXDENSITYUNIT": "TESLA",
"MAGNETICFLUXUNIT": "WEBER",
"MASSUNIT": "GRAM",
"PLANEANGLEUNIT": "RADIAN",
"POWERUNIT": "WATT",
"PRESSUREUNIT": "PASCAL",
"RADIOACTIVITYUNIT": "BECQUEREL",
"SOLIDANGLEUNIT": "STERADIAN",
"THERMODYNAMICTEMPERATUREUNIT": "KELVIN", # Or, DEGREE_CELSIUS, but this is a quirk of IFC
"TIMEUNIT": "SECOND",
"VOLUMEUNIT": "CUBIC_METRE",
}
# Are you good at physics? Want to help fill these in? :)
named_dimensions = {
# "ABSORBEDDOSEUNIT": (0, 0, 0, 0, 0, 0, 0),
"AMOUNTOFSUBSTANCEUNIT": (0, 0, 0, 0, 0, 1, 0),
"AREAUNIT": (2, 0, 0, 0, 0, 0, 0),
# "DOSEEQUIVALENTUNIT": (0, 0, 0, 0, 0, 0, 0),
# "ELECTRICCAPACITANCEUNIT": (0, 0, 0, 0, 0, 0, 0),
# "ELECTRICCHARGEUNIT": (0, 0, 0, 0, 0, 0, 0),
# "ELECTRICCONDUCTANCEUNIT": (0, 0, 0, 0, 0, 0, 0),
"ELECTRICCURRENTUNIT": (0, 0, 0, 1, 0, 0, 0),
# "ELECTRICRESISTANCEUNIT": (0, 0, 0, 0, 0, 0, 0),
# "ELECTRICVOLTAGEUNIT": (0, 0, 0, 0, 0, 0, 0),
"ENERGYUNIT": (2, 1, -2, 0, 0, 0, 0),
"FORCEUNIT": (1, 1, -2, 0, 0, 0, 0),
"FREQUENCYUNIT": (0, 0, -1, 0, 0, 0, 0),
# "ILLUMINANCEUNIT": (0, 0, 0, 0, 0, 0, 0),
# "INDUCTANCEUNIT": (0, 0, 0, 0, 0, 0, 0),
"LENGTHUNIT": (1, 0, 0, 0, 0, 0, 0),
# "LUMINOUSFLUXUNIT": (0, 0, 0, 0, 0, 0, 0),
"LUMINOUSINTENSITYUNIT": (0, 0, 0, 0, 0, 0, 1),
# "MAGNETICFLUXDENSITYUNIT": (0, 0, 0, 0, 0, 0, 0),
# "MAGNETICFLUXUNIT": (0, 0, 0, 0, 0, 0, 0),
"MASSUNIT": (0, 1, 0, 0, 0, 0, 0),
"PLANEANGLEUNIT": (0, 0, 0, 0, 0, 0, 0),
"POWERUNIT": (2, 1, -3, 0, 0, 0, 0),
"PRESSUREUNIT": (-1, 1, -2, 0, 0, 0, 0),
# "RADIOACTIVITYUNIT": (0, 0, 0, 0, 0, 0, 0),
"SOLIDANGLEUNIT": (0, 0, 0, 0, 0, 0, 0),
"THERMODYNAMICTEMPERATUREUNIT": (0, 0, 0, 0, 1, 0, 0),
"TIMEUNIT": (0, 0, 1, 0, 0, 0, 0),
"VOLUMEUNIT": (3, 0, 0, 0, 0, 0, 0),
}
si_conversions = {
"inch": 0.0254,
"foot": 0.3048,
@@ -169,7 +234,7 @@ def get_prefix_multiplier(text):
def get_unit_name(text):
text = text.upper().replace("METER", "METRE")
for name in unit_names:
if name in text:
if name.replace("_", " ") in text:
return name
@@ -177,6 +242,10 @@ def get_si_dimensions(name):
return si_dimensions.get(name, si_dimensions["OTHERWISE"])
def get_named_dimensions(name):
return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0))
def get_property_unit(prop, ifc_file):
unit = getattr(prop, "Unit", None)
if unit:
@@ -197,6 +266,10 @@ def get_property_unit(prop, ifc_file):
return units[0]
def get_unit_measure_type(unit_type):
return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure"
def get_symbol_quantity_class(symbol):
# Dumb, but everybody gets it, unlike regex golf
if not symbol:
@@ -259,19 +332,17 @@ def convert(value, from_prefix, from_unit, to_prefix, to_unit):
return value
"""Returns a unit scale factor to convert to and from IFC project length units and SI meters
Example::
ifc_project_length * unit_scale = si_meters
si_meters / unit_scale = ifc_project_length
:returns: The scale factor
:rtype: float
"""
def calculate_unit_scale(file):
"""Returns a unit scale factor to convert to and from IFC project length units and SI meters
Example::
ifc_project_length * unit_scale = si_meters
si_meters / unit_scale = ifc_project_length
:returns: The scale factor
:rtype: float
"""
units = file.by_type("IfcUnitAssignment")[0]
unit_scale = 1
for unit in units.Units:
@@ -34,7 +34,9 @@ class json_logger:
self.instance = instance
def log(self, level, message, *args, **kwargs):
self.statements.append(log_entry_type(level, message % args, kwargs.get("instance"))._asdict())
self.statements.append(
log_entry_type(level, message % args, kwargs.get("instance"))._asdict()
)
def __getattr__(self, level):
return functools.partial(self.log, level, instance=self.instance)
@@ -81,7 +83,9 @@ def assert_valid(attr, val, schema):
if isinstance(attr_type, simple_type):
invalid = type(val) != simple_type_python_mapping[attr_type.declared_type()]
elif isinstance(attr_type, (entity_type, type_declaration)):
invalid = not isinstance(val, ifcopenshell.entity_instance) or not val.is_a(attr_type.name())
invalid = not isinstance(val, ifcopenshell.entity_instance) or not val.is_a(
attr_type.name()
)
elif isinstance(attr_type, select_type):
val_to_use = val
if isinstance(schema.declaration_by_name(val.is_a()), enumeration_type):
@@ -90,13 +94,19 @@ def assert_valid(attr, val, schema):
else:
invalid = True
if not invalid:
invalid = not any(try_valid(x, val_to_use, schema) for x in attr_type.select_list())
invalid = not any(
try_valid(x, val_to_use, schema) for x in attr_type.select_list()
)
elif isinstance(attr_type, enumeration_type):
invalid = val not in attr_type.enumeration_items()
elif isinstance(attr_type, aggregation_type):
b1, b2 = attr_type.bound1(), attr_type.bound2()
ty = attr_type.type_of_element()
invalid = len(val) < b1 or (b2 != -1 and len(val) > b2) or not all(assert_valid(ty, v, schema) for v in val)
invalid = (
len(val) < b1
or (b2 != -1 and len(val) > b2)
or not all(assert_valid(ty, v, schema) for v in val)
)
else:
raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type))
@@ -135,6 +145,7 @@ def validate(f, logger):
logger.set_instance(inst)
entity = schema.declaration_by_name(inst.is_a())
attrs = entity.all_attributes()
if entity.is_abstract():
e = "Entity %s is abstract" % entity.name()
@@ -143,20 +154,38 @@ def validate(f, logger):
else:
logger.error("In %s\n%s", inst, e)
for attr, val, is_derived in zip(entity.all_attributes(), inst, entity.derived()):
has_invalid_value = False
for i in range(len(attrs)):
try:
inst[i]
pass
except:
if hasattr(logger, "set_instance"):
logger.error("Invalid attribute value for %s.%s", entity, attrs[i])
else:
logger.error(
"In %s\nInvalid attribute value for %s.%s",
inst,
entity,
attrs[i],
)
has_invalid_value = True
if val is None and not (is_derived or attr.optional()):
logger.error("Attribute %s.%s not optional", entity, attr)
if not has_invalid_value:
for attr, val, is_derived in zip(attrs, inst, entity.derived()):
if val is not None:
attr_type = attr.type_of_attribute()
try:
assert_valid(attr, val, schema)
except ValidationError as e:
if hasattr(logger, "set_instance"):
logger.error(str(e))
else:
logger.error("In %s\n%s", inst, e)
if val is None and not (is_derived or attr.optional()):
logger.error("Attribute %s.%s not optional", entity, attr)
if val is not None:
attr_type = attr.type_of_attribute()
try:
assert_valid(attr, val, schema)
except ValidationError as e:
if hasattr(logger, "set_instance"):
logger.error(str(e))
else:
logger.error("In %s\n%s", inst, e)
for attr in entity.all_inverse_attributes():
val = getattr(inst, attr.name())
+18 -1
View File
@@ -24,6 +24,10 @@
#include <set>
#include <iterator>
#include <boost/unordered_map.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include "ifc_parse_api.h"
@@ -132,7 +136,16 @@ private:
void build_inverses_(IfcUtil::IfcBaseClass*);
std::set<int> batch_deletion_ids_;
typedef boost::multi_index_container<
int,
boost::multi_index::indexed_by<
boost::multi_index::sequenced<>,
boost::multi_index::ordered_unique<
boost::multi_index::identity<int>
>
>
> batch_deletion_ids_t;
batch_deletion_ids_t batch_deletion_ids_;
bool batch_mode_ = false;
void process_deletion_();
@@ -220,6 +233,10 @@ public:
/// in the first function argument.
aggregate_of_instance::ptr traverse(IfcUtil::IfcBaseClass* instance, int max_level=-1);
/// Same as traverse() but maintains topological order by using a
/// breadth-first search
aggregate_of_instance::ptr traverse_breadth_first(IfcUtil::IfcBaseClass* instance, int max_level=-1);
aggregate_of_instance::ptr getInverse(int instance_id, const IfcParse::declaration* type, int attribute_index);
/// Marks entity as modified so that potential cache for it is invalidated.
+62 -11
View File
@@ -1538,15 +1538,52 @@ void IfcFile::recalculate_id_counter() {
MaxId = (unsigned int)k;
}
class traversal_recorder {
aggregate_of_instance::ptr list_;
std::map<int, aggregate_of_instance::ptr> instances_by_level_;
int mode_;
public:
traversal_recorder(int mode) : mode_(mode) {
if (mode == 0) {
list_.reset(new aggregate_of_instance);
}
};
void push_back(int level, IfcUtil::IfcBaseClass* instance) {
if (mode_ == 0) {
list_->push(instance);
} else {
auto& l = instances_by_level_[level];
if (!l) {
l.reset(new aggregate_of_instance);
}
l->push(instance);
}
}
aggregate_of_instance::ptr get_list() const {
if (mode_ == 0) {
return list_;
} else {
aggregate_of_instance::ptr l(new aggregate_of_instance);
for (auto& p : instances_by_level_) {
l->push(p.second);
}
return l;
}
}
};
class traversal_visitor {
private:
std::set<IfcUtil::IfcBaseClass*>& visited_;
aggregate_of_instance::ptr& list_;
traversal_recorder& list_;
int level_;
int max_level_;
public:
traversal_visitor(std::set<IfcUtil::IfcBaseClass*>& visited, aggregate_of_instance::ptr& list, int level, int max_level)
traversal_visitor(std::set<IfcUtil::IfcBaseClass*>& visited, traversal_recorder& list, int level, int max_level)
: visited_(visited)
, list_(list)
, level_(level)
@@ -1556,12 +1593,12 @@ public:
void operator()(IfcUtil::IfcBaseClass* inst);
};
void traverse_(IfcUtil::IfcBaseClass* instance, std::set<IfcUtil::IfcBaseClass*>& visited, aggregate_of_instance::ptr list, int level, int max_level) {
void traverse_(IfcUtil::IfcBaseClass* instance, std::set<IfcUtil::IfcBaseClass*>& visited, traversal_recorder& list, int level, int max_level) {
if (visited.find(instance) != visited.end()) {
return;
}
visited.insert(instance);
list->push(instance);
list.push_back(level, instance);
if (level >= max_level && max_level > 0) return;
@@ -1575,9 +1612,18 @@ void traversal_visitor::operator()(IfcUtil::IfcBaseClass* inst) {
aggregate_of_instance::ptr IfcParse::traverse(IfcUtil::IfcBaseClass* instance, int max_level) {
std::set<IfcUtil::IfcBaseClass*> visited;
aggregate_of_instance::ptr return_value(new aggregate_of_instance);
traverse_(instance, visited, return_value, 0, max_level);
return return_value;
traversal_recorder r(0);
traverse_(instance, visited, r, 0, max_level);
return r.get_list();
}
// I'm cheating this isn't breadth-first, but rather we record visited instances
// keeping track of their rank and return a list ordered by rank. Is this equivalent?
aggregate_of_instance::ptr IfcParse::traverse_breadth_first(IfcUtil::IfcBaseClass* instance, int max_level) {
std::set<IfcUtil::IfcBaseClass*> visited;
traversal_recorder r(1);
traverse_(instance, visited, r, 0, max_level);
return r.get_list();
}
/// @note: for backwards compatibility
@@ -1585,6 +1631,11 @@ aggregate_of_instance::ptr IfcFile::traverse(IfcUtil::IfcBaseClass* instance, in
return IfcParse::traverse(instance, max_level);
}
/// @note: for backwards compatibility
aggregate_of_instance::ptr IfcFile::traverse_breadth_first(IfcUtil::IfcBaseClass* instance, int max_level) {
return IfcParse::traverse_breadth_first(instance, max_level);
}
void IfcFile::mark_entity_as_modified(int /*id*/)
{
by_ref_cached_.clear();
@@ -1857,7 +1908,7 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
throw IfcParse::IfcException("Instance not part of this file");
}
batch_deletion_ids_.insert(id);
batch_deletion_ids_.push_back(id);
if (!batch_mode_) {
process_deletion_();
@@ -1866,7 +1917,7 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
void IfcFile::process_deletion_() {
for (auto& id : batch_deletion_ids_) {
for (auto& id : batch_deletion_ids_.get<0>()) {
auto entity = instance_by_id(id);
aggregate_of_instance::ptr references = instances_by_reference(id);
@@ -2012,10 +2063,10 @@ void IfcFile::process_deletion_() {
if (batch_mode_) {
for (auto it = byref.begin(); it != byref.end();) {
bool do_delete = batch_deletion_ids_.find(it->first) != batch_deletion_ids_.end();
bool do_delete = batch_deletion_ids_.get<1>().find(it->first) != batch_deletion_ids_.get<1>().end();
if (!do_delete) {
it->second.erase(std::remove_if(it->second.begin(), it->second.end(), [this](int x) {
return batch_deletion_ids_.find(x) != batch_deletion_ids_.end();
return batch_deletion_ids_.get<1>().find(x) != batch_deletion_ids_.get<1>().end();
}), it->second.end());
do_delete = it->second.empty();
}
+2
View File
@@ -273,6 +273,8 @@ namespace IfcParse {
IFC_PARSE_API IfcEntityInstanceData* read(unsigned int i, IfcFile* t, boost::optional<unsigned> offset = boost::none);
IFC_PARSE_API aggregate_of_instance::ptr traverse(IfcUtil::IfcBaseClass* instance, int max_level = -1);
IFC_PARSE_API aggregate_of_instance::ptr traverse_breadth_first(IfcUtil::IfcBaseClass* instance, int max_level = -1);
}
IFC_PARSE_API std::ostream& operator<< (std::ostream& os, const IfcParse::IfcFile& f);
@@ -29,6 +29,8 @@ class Patcher:
self.args = args
def patch(self):
if self.file.schema == "IFC2X3":
return
curve_map = {}
for curve in self.file.by_type("IfcIndexedPolyCurve"):
+5 -1
View File
@@ -421,7 +421,11 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
}
} else {
if (!representation) {
if (instance->declaration().is(Schema::IfcRepresentationItem::Class()) || instance->declaration().is(Schema::IfcRepresentation::Class())) {
if (instance->declaration().is(Schema::IfcRepresentationItem::Class()) ||
instance->declaration().is(Schema::IfcRepresentation::Class()) ||
// https://github.com/IfcOpenShell/IfcOpenShell/issues/1649
instance->declaration().is(Schema::IfcProfileDef::Class())
) {
IfcGeom::IfcRepresentationShapeItems shapes = kernel.convert(instance);
IfcGeom::ElementSettings element_settings(settings, kernel.getValue(IfcGeom::Kernel::GV_LENGTH_UNIT), instance->declaration().name());