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 Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`. is available via `bcfapi.py`.
- BCF-XML version 2.1: Fully supported - BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0 - 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-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-API version 3.0: Almost fully supported, except for two requests.
## bcfxml ## bcfxml
@@ -73,9 +73,9 @@ auth_methods = foundation_client.get_auth_methods()
# Our library currently only implements the authorization_code flow # Our library currently only implements the authorization_code flow
if "authorization_code" in auth_methods: 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() versions = foundation_client.get_versions()
for version in versions: for version in versions:
@@ -94,7 +94,8 @@ print(data)
``` ```
## Todo List ## 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"] self.baseurl = version["api_base_url"]
def get(self, endpoint, params=None, is_auth_required=False): 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()} headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
return requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None).json() 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/ cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality # 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* cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/ 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(): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)): 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 continue
new = props.add() new = props.add()
new.name = attribute.name() new.name = attribute.name()
@@ -65,20 +66,21 @@ def import_attributes(ifc_class, props, data, callback=None):
def export_attributes(props, callback=None): def export_attributes(props, callback=None):
attributes = {} attributes = {}
for attribute in props: for prop in props:
is_handled_by_callback = callback(attributes, attribute) if callback else False is_handled_by_callback = callback(attributes, prop) if callback else False
if attribute.is_null: if is_handled_by_callback:
attributes[attribute.name] = None continue # Our job is done
elif is_handled_by_callback:
pass # Our job is done if prop.is_null:
elif attribute.data_type == "string": attributes[prop.name] = None
attributes[attribute.name] = attribute.string_value elif prop.data_type == "string":
elif attribute.data_type == "boolean": attributes[prop.name] = prop.string_value
attributes[attribute.name] = attribute.bool_value elif prop.data_type == "boolean":
elif attribute.data_type == "integer": attributes[prop.name] = prop.bool_value
attributes[attribute.name] = attribute.int_value elif prop.data_type == "integer":
elif attribute.data_type == "float": attributes[prop.name] = prop.int_value
attributes[attribute.name] = attribute.float_value elif prop.data_type == "float":
elif attribute.data_type == "enum": attributes[prop.name] = prop.float_value
attributes[attribute.name] = attribute.enum_value elif prop.data_type == "enum":
attributes[prop.name] = prop.enum_value
return attributes return attributes
@@ -26,8 +26,8 @@ classes = (
operator.ExpandCostItem, operator.ExpandCostItem,
operator.ContractCostItem, operator.ContractCostItem,
operator.RemoveCostItem, operator.RemoveCostItem,
operator.AssignCostItemProduct, operator.AssignCostItemQuantity,
operator.UnassignCostItemProduct, operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity, operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity, operator.RemoveCostItemQuantity,
operator.AddCostValue, operator.AddCostValue,
@@ -36,11 +36,15 @@ classes = (
operator.SelectCostItemProducts, operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts, operator.SelectCostScheduleProducts,
operator.ImportCostScheduleCsv, operator.ImportCostScheduleCsv,
operator.LoadCostItemQuantities,
prop.CostItem, prop.CostItem,
prop.CostItemQuantity,
prop.BIMCostProperties, prop.BIMCostProperties,
ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_schedules,
ui.BIM_PT_cost_item_quantities,
ui.BIM_UL_cost_items, ui.BIM_UL_cost_items,
ui.BIM_UL_cost_columns, 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 blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
class AddCostSchedule(bpy.types.Operator): class AddCostSchedule(bpy.types.Operator):
@@ -100,22 +101,9 @@ class EnableEditingCostItems(bpy.types.Operator):
def execute(self, context): def execute(self, context):
if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound: if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound:
# lol self.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.props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
self.props.is_cost_update_enabled = False
self.props.active_cost_schedule_id = self.cost_schedule self.props.active_cost_schedule_id = self.cost_schedule
while len(self.props.cost_items) > 0: while len(self.props.cost_items) > 0:
self.props.cost_items.remove(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"]: for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.create_new_cost_item_li(related_object_id, 0) self.create_new_cost_item_li(related_object_id, 0)
self.props.is_editing = "COST_ITEMS" self.props.is_editing = "COST_ITEMS"
self.props.is_cost_update_enabled = True
return {"FINISHED"} 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): def create_new_cost_item_li(self, related_object_id, level_index):
cost_item = Data.cost_items[related_object_id] cost_item = Data.cost_items[related_object_id]
new = self.props.cost_items.add() new = self.props.cost_items.add()
new.ifc_definition_id = related_object_id new.ifc_definition_id = related_object_id
new.name = cost_item["Name"] or "Unnamed" 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.is_expanded = related_object_id not in self.contracted_cost_items
new.level_index = level_index new.level_index = level_index
if cost_item["IsNestedBy"]: if cost_item["IsNestedBy"]:
@@ -297,61 +303,81 @@ class EditCostItem(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class AssignCostItemProduct(bpy.types.Operator): class AssignCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.assign_cost_item_product" bl_idname = "bim.assign_cost_item_quantity"
bl_label = "Assign Control" bl_label = "Assign Cost Item Quantity"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty() 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): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(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() 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( ifcopenshell.api.run(
"cost.assign_cost_item_product", "cost.assign_cost_item_quantity",
self.file, self.file,
cost_item=self.file.by_id(self.cost_item), cost_item=self.file.by_id(self.cost_item),
products=[ products=products,
self.file.by_id(o.BIMObjectProperties.ifc_definition_id) prop_name=self.prop_name,
for o in related_objects
if o.BIMObjectProperties.ifc_definition_id
],
) )
Data.load(self.file) Data.load(self.file)
bpy.ops.bim.load_cost_item_quantities()
return {"FINISHED"} return {"FINISHED"}
class UnassignCostItemProduct(bpy.types.Operator): class UnassignCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.unassign_cost_item_product" bl_idname = "bim.unassign_cost_item_quantity"
bl_label = "Unassign Control" bl_label = "Unassign Cost Item Quantity"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty() cost_item: bpy.props.IntProperty()
related_object: bpy.props.StringProperty() related_object: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(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() 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( ifcopenshell.api.run(
"cost.unassign_cost_item_product", "cost.unassign_cost_item_quantity",
self.file, self.file,
cost_item=self.file.by_id(self.cost_item), cost_item=self.file.by_id(self.cost_item),
products=[ products=products,
self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
for o in related_objects
if o.BIMObjectProperties.ifc_definition_id
],
) )
Data.load(self.file) Data.load(self.file)
bpy.ops.bim.load_cost_item_quantities()
return {"FINISHED"} return {"FINISHED"}
@@ -396,21 +422,10 @@ class AddCostItemQuantity(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
self.props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
if self.props.quantity_types == "QTO": self.add_manual_quantity()
self.add_quantities_from_qto_filter()
else:
self.add_manual_quantity()
Data.load(self.file) Data.load(self.file)
return {"FINISHED"} 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): def add_manual_quantity(self):
ifcopenshell.api.run( ifcopenshell.api.run(
"cost.add_cost_item_quantity", "cost.add_cost_item_quantity",
@@ -557,6 +572,35 @@ class EnableEditingCostItemValue(bpy.types.Operator):
prop.data_type = "float" prop.data_type = "float"
prop.float_value = 0.0 if prop.is_null else data[name] prop.float_value = 0.0 if prop.is_null else data[name]
return True 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): class DisableEditingCostItemValue(bpy.types.Operator):
@@ -581,7 +625,7 @@ class EditCostValue(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMCostProperties 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() self.file = IfcStore.get_file()
ifcopenshell.api.run( ifcopenshell.api.run(
"cost.edit_cost_value", "cost.edit_cost_value",
@@ -592,6 +636,21 @@ class EditCostValue(bpy.types.Operator):
bpy.ops.bim.disable_editing_cost_item_value() bpy.ops.bim.disable_editing_cost_item_value()
return {"FINISHED"} 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): class CopyCostItemValues(bpy.types.Operator):
bl_idname = "bim.copy_cost_item_values" bl_idname = "bim.copy_cost_item_values"
@@ -622,7 +681,7 @@ class SelectCostItemProducts(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() 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: for obj in context.visible_objects:
obj.select_set(False) obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products: if obj.BIMObjectProperties.ifc_definition_id in related_products:
@@ -649,7 +708,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def get_related_products(self, cost_item): 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"]: for child_id in cost_item["IsNestedBy"]:
self.get_related_products(Data.cost_items[child_id]) 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.set_categories([c.name for c in self.props.columns])
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
return {"FINISHED"} 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 = [] quantitytypes_enum = []
quantitynames_enum = [] productquantitynames_enum = []
productquantitynames_count = []
processquantitynames_enum = []
processquantitynames_id = 0
resourcequantitynames_enum = []
resourcequantitynames_id = 0
def purge(): def purge():
global quantitytypes_enum 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 = [] quantitytypes_enum = []
quantitynames_enum = [] productquantitynames_enum = []
productquantitynames_count = []
processquantitynames_enum = []
processquantitynames_id = 0
resourcequantitynames_enum = []
resourcequantitynames_id = 0
def getQuantityTypes(self, context): def getQuantityTypes(self, context):
global quantitytypes_enum global quantitytypes_enum
if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")] quantitytypes_enum = []
quantitytypes_enum.extend( quantitytypes_enum.extend(
[ [
(t.name(), t.name(), "") (t.name(), t.name(), "")
@@ -41,26 +56,99 @@ def getQuantityTypes(self, context):
return quantitytypes_enum return quantitytypes_enum
def getQuantityNames(self, context): def getProductQuantityNames(self, context):
global quantitynames_enum global productquantitynames_enum
global productquantitynames_count
ifc_file = IfcStore.get_file() 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() 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: 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"]: for qto_id in PsetData.products[element_id]["qtos"]:
qto = PsetData.qtos[qto_id] qto = PsetData.qtos[qto_id]
[names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]] [potential_names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
quantitynames_enum.extend([(n, n, "") for n in names]) names = names.intersection(potential_names) if names else potential_names
return quantitynames_enum 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): def updateCostItemName(self, context):
if self.name == "Unnamed": props = context.scene.BIMCostProperties
if not props.is_cost_update_enabled or self.name == "Unnamed":
return return
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
props = context.scene.BIMCostProperties
ifcopenshell.api.run( ifcopenshell.api.run(
"cost.edit_cost_item", "cost.edit_cost_item",
self.file, self.file,
@@ -74,24 +162,34 @@ def updateCostItemName(self, context):
class CostItem(PropertyGroup): class CostItem(PropertyGroup):
name: StringProperty(name="Name", update=updateCostItemName) name: StringProperty(name="Name", update=updateCostItemName)
identification: StringProperty(name="Identification", update=updateCostItemIdentification)
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children") has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded") is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index") 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): 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) cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing") is_editing: StringProperty(name="Is Editing")
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id") active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
cost_items: CollectionProperty(name="Cost Items", type=CostItem) cost_items: CollectionProperty(name="Cost Items", type=CostItem)
active_cost_item_id: IntProperty(name="Active Cost Id") active_cost_item_id: IntProperty(name="Active Cost Id")
cost_item_editing_type: StringProperty(name="Cost Item Editing Type") 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) cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]") contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types") 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") active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id")
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
cost_types: EnumProperty( cost_types: EnumProperty(
@@ -109,3 +207,9 @@ class BIMCostProperties(PropertyGroup):
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False) should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
columns: CollectionProperty(name="Columns", type=StrProperty) columns: CollectionProperty(name="Columns", type=StrProperty)
active_column_index: IntProperty(name="Active Column Index") 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 bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.cost.data import Data 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="") 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): 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( self.layout.template_list(
"BIM_UL_cost_items", "BIM_UL_cost_items",
"", "",
@@ -108,8 +135,6 @@ class BIM_PT_cost_schedules(Panel):
def draw_editable_cost_item_quantities_ui(self): def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(self.props, "quantity_types", text="") 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 = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id op.cost_item = self.props.active_cost_item_id
op.ifc_class = self.props.quantity_types 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="") 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): class BIM_UL_cost_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
cost_item = Data.cost_items[item.ifc_definition_id] cost_item = Data.cost_items[item.ifc_definition_id]
row = layout.row(align=True) row = layout.row(align=True)
@@ -265,51 +392,30 @@ class BIM_UL_cost_items(UIList):
else: else:
row.label(text="", icon="DOT") row.label(text="", icon="DOT")
split1 = row.split(factor=0.7) split1 = row.split(factor=0.1)
split1.prop(item, "name", emboss=False, text="") 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") if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES":
op.cost_item = item.ifc_definition_id split2.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})")
row.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol']})")
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC") split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
op.cost_item = item.ifc_definition_id
row.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
for column in props.columns: for column in self.props.columns:
row.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) 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: def draw_buttons(self, row, item, cost_item):
oprops = context.active_object.BIMObjectProperties pass # TODO: reimplement somewhere with better UX
row = layout.row(align=True) # elif self.props.active_cost_item_id:
if oprops.ifc_definition_id in cost_item["Controls"]: # if self.props.cost_item_editing_type == "VALUES":
op = row.operator("bim.unassign_cost_item_product", text="", icon="KEYFRAME_HLT", emboss=False) # op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN")
op.cost_item = item.ifc_definition_id # op.source = self.props.active_cost_item_id
else: # op.destination = item.ifc_definition_id
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
class BIM_UL_cost_columns(UIList): class BIM_UL_cost_columns(UIList):
@@ -319,3 +425,18 @@ class BIM_UL_cost_columns(UIList):
row = layout.row(align=True) row = layout.row(align=True)
row.prop(item, "name", emboss=False, text="") row.prop(item, "name", emboss=False, text="")
row.operator("bim.remove_cost_column", text="", icon="X").name = item.name 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())
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
self.set_default_material(obj, element)
return {"FINISHED"} 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): class UnassignMaterial(bpy.types.Operator):
bl_idname = "bim.unassign_material" bl_idname = "bim.unassign_material"
@@ -546,25 +529,8 @@ class EditAssignedMaterial(bpy.types.Operator):
elif material_set.is_a("IfcMaterialProfileSet"): elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles() Data.load_profiles()
bpy.ops.bim.disable_editing_assigned_material(obj=obj.name) 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"} 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): class EnableEditingMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_material_set_item" 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 from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace
classes = ( classes = (
product.AddEmptyType,
product.AddTypeInstance, product.AddTypeInstance,
product.AlignProduct, product.AlignProduct,
product.DynamicallyVoidProduct, product.DynamicallyVoidProduct,
@@ -10,8 +11,7 @@ classes = (
wall.AlignWall, wall.AlignWall,
wall.FlipWall, wall.FlipWall,
wall.SplitWall, wall.SplitWall,
wall.AddWallOpening, opening.AddElementOpening,
slab.AddSlabOpening,
profile.ExtendProfile, profile.ExtendProfile,
prop.BIMModelProperties, prop.BIMModelProperties,
ui.BIM_PT_authoring, 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(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.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_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) bpy.app.handlers.load_post.append(handler.load_post)
wm = bpy.context.window_manager wm = bpy.context.window_manager
if wm.keyconfigs.addon: 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(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.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_mesh_add.remove(opening.add_object_button)
bpy.types.VIEW3D_MT_add.remove(product.add_empty_type_button)
wm = bpy.context.window_manager wm = bpy.context.window_manager
kc = wm.keyconfigs.addon kc = wm.keyconfigs.addon
if kc: if kc:
@@ -1,20 +1,36 @@
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.api 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 blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
@persistent @persistent
def load_post(*args): def load_post(*args):
ifcopenshell.api.add_pre_listener( ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
"attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name
)
ifcopenshell.api.add_post_listener( ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box "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( ifcopenshell.api.add_post_listener(
"material.edit_profile_usage", "material.edit_profile_usage",
"BlenderBIM.Product.RegenerateProfileUsage", "BlenderBIM.Product.RegenerateProfileUsage",
@@ -22,8 +38,10 @@ def load_post(*args):
) )
IfcStore.add_element_listener(opening.element_listener) IfcStore.add_element_listener(opening.element_listener)
IfcStore.add_element_listener(wall.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( ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid "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 "type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
) )
IfcStore.add_element_listener(slab.element_listener)
ifcopenshell.api.add_pre_listener( ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid "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 "type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
) )
IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_pre_listener( ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid "geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
) )
@@ -52,6 +52,46 @@ def mode_callback(obj, data):
bm.free() 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): def add_object(self, context):
bm = bmesh.new() bm = bmesh.new()
bmesh.ops.create_cube(bm, size=self.size) bmesh.ops.create_cube(bm, size=self.size)
@@ -8,6 +8,33 @@ from . import wall, slab, profile
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector, Matrix 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): class AddTypeInstance(bpy.types.Operator):
@@ -214,3 +241,36 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
bpy.ops.bim.switch_representation( bpy.ops.bim.switch_representation(
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True 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): 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 ( if (
obj.mode != "EDIT" not obj.data
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve)) or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring 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") parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3": if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return return
IfcStore.edited_objs.add(obj) if obj.mode == "EDIT":
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"] IfcStore.edited_objs.add(obj)
if modifier: modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
return if modifier:
depth = obj.dimensions.z return
bm = bmesh.from_edit_mesh(obj.data) depth = obj.dimensions.z
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table() bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
non_bottom_faces = [] bm.faces.ensure_lookup_table()
for face in bm.faces: non_bottom_faces = []
if face.normal.z > -0.9: for face in bm.faces:
non_bottom_faces.append(face) if face.normal.z > -0.9:
else: non_bottom_faces.append(face)
face.normal_flip() else:
bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES") face.normal_flip()
bmesh.update_edit_mesh(obj.data) bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
bm.free() bmesh.update_edit_mesh(obj.data)
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY") bm.free()
modifier.use_even_offset = True modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.offset = 1 modifier.use_even_offset = True
modifier.thickness = depth 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): 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) 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: class DumbSlabGenerator:
def __init__(self, relating_type): def __init__(self, relating_type):
self.relating_type = relating_type self.relating_type = relating_type
@@ -249,7 +221,7 @@ class DumbSlabGenerator:
if material.is_a("IfcMaterialLayerSet"): if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers] thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break break
if not thicknesses: if not sum(thicknesses):
return return
self.collection = bpy.context.view_layer.active_layer_collection.collection 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): def mode_callback(obj, data):
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]): for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if ( if (
obj.mode != "EDIT" not obj.data
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve)) or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring 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") parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2": if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return return
bpy.ops.bim.dynamically_void_product(obj=obj.name) if obj.mode == "EDIT":
IfcStore.edited_objs.add(obj) bpy.ops.bim.dynamically_void_product(obj=obj.name)
bm = bmesh.from_edit_mesh(obj.data) IfcStore.edited_objs.add(obj)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) bm = bmesh.from_edit_mesh(obj.data)
bmesh.update_edit_mesh(obj.data) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bm.free() bmesh.update_edit_mesh(obj.data)
bm.free()
else:
class AddWallOpening(bpy.types.Operator): new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
bl_idname = "bim.add_wall_opening" obj.data.transform(
bl_label = "Add Wall Opening" Matrix.Translation(
bl_options = {"REGISTER", "UNDO"} (obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
)
def execute(self, context): )
return IfcStore.execute_ifc_operator(self, context) obj.matrix_world.translation = new_origin
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"}
class JoinWall(bpy.types.Operator): class JoinWall(bpy.types.Operator):
@@ -643,7 +615,7 @@ class DumbWallGenerator:
if material.is_a("IfcMaterialLayerSet"): if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers] thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break break
if not thicknesses: if not sum(thicknesses):
return return
self.collection = bpy.context.view_layer.active_layer_collection.collection self.collection = bpy.context.view_layer.active_layer_collection.collection
@@ -960,15 +932,19 @@ class DumbWallPlaner:
IfcStore.edited_objs.add(obj) IfcStore.edited_objs.add(obj)
def thicken_face(self, face, delta_thickness): def thicken_face(self, face, delta_thickness):
slide_magnitude = abs(delta_thickness) / 2 slide_magnitude = abs(delta_thickness)
for vert in face.verts: for vert in face.verts:
slide_vector = None slide_vector = None
for edge in vert.link_edges: for edge in vert.link_edges:
other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0] other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0]
if delta_thickness > 0: if delta_thickness > 0:
potential_slide_vector = vert.co - other_vert.co potential_slide_vector = vert.co - other_vert.co
if potential_slide_vector.y < 0:
continue
else: else:
potential_slide_vector = other_vert.co - vert.co 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: if abs(potential_slide_vector.x) > 0.9 or abs(potential_slide_vector.z) > 0.9:
continue continue
slide_vector = potential_slide_vector slide_vector = potential_slide_vector
@@ -64,14 +64,6 @@ class BimTool(WorkSpaceTool):
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="Split", icon="EVENT_S") 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"]: if props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
row = layout.row() row = layout.row()
@@ -80,6 +72,10 @@ class BimTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E") 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 = layout.row(align=True)
row.label(text="Align") row.label(text="Align")
row = layout.row(align=True) row = layout.row(align=True)
@@ -142,10 +138,7 @@ class Hotkey(bpy.types.Operator):
bpy.ops.bim.align_product(align_type="NEGATIVE") bpy.ops.bim.align_product(align_type="NEGATIVE")
def hotkey_S_O(self): def hotkey_S_O(self):
if self.props.ifc_class == "IfcWallType": bpy.ops.bim.add_element_opening()
bpy.ops.bim.add_wall_opening()
elif self.props.ifc_class == "IfcSlabType":
bpy.ops.bim.add_slab_opening()
def hotkey_A_D(self): def hotkey_A_D(self):
bpy.ops.bim.toggle_decomposition_parenting() 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(): def register():
pass bpy.types.Scene.BIMProfileProperties = bpy.props.PointerProperty(type=prop.BIMProfileProperties)
def unregister(): 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 = "" self.props.active_library_element = ""
types = IfcStore.library_file.wrapped_data.types_with_super() 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: if importable_type in types:
new = self.props.library_elements.add() new = self.props.library_elements.add()
new.name = importable_type 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: if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
for element in elements: for element in elements:
new = self.props.library_elements.add() 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() new.ifc_definition_id = element.id()
if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"): if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"):
new.is_declared = False new.is_declared = False
@@ -11,17 +11,30 @@ classes = (
operator.AddQto, operator.AddQto,
operator.GuessQuantity, operator.GuessQuantity,
prop.PsetProperties, prop.PsetProperties,
prop.MaterialPsetProperties,
prop.TaskPsetProperties,
prop.ResourcePsetProperties,
prop.ProfilePsetProperties,
ui.BIM_PT_object_psets, ui.BIM_PT_object_psets,
ui.BIM_PT_object_qtos, ui.BIM_PT_object_qtos,
ui.BIM_PT_material_psets, ui.BIM_PT_material_psets,
ui.BIM_PT_task_qtos,
ui.BIM_PT_resource_qtos,
ui.BIM_PT_profile_psets,
) )
def register(): def register():
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties) 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(): def unregister():
del bpy.types.Object.PsetProperties del bpy.types.Object.PsetProperties
del bpy.types.Material.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 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): class TogglePsetExpansion(bpy.types.Operator):
bl_idname = "bim.toggle_pset_expansion" bl_idname = "bim.toggle_pset_expansion"
bl_label = "Toggle Pset Expansion" bl_label = "Toggle Pset Expansion"
@@ -32,11 +69,7 @@ class EnablePsetEditing(bpy.types.Operator):
obj_type: bpy.props.StringProperty() obj_type: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
if self.obj_type == "Object": self.props = get_pset_props(context, self.obj, self.obj_type)
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
self.props = obj.PsetProperties
while len(self.props.properties) > 0: while len(self.props.properties) > 0:
self.props.properties.remove(0) self.props.properties.remove(0)
@@ -138,11 +171,7 @@ class DisablePsetEditing(bpy.types.Operator):
obj_type: bpy.props.StringProperty() obj_type: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
if self.obj_type == "Object": props = get_pset_props(context, self.obj, self.obj_type)
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
props = obj.PsetProperties
props.active_pset_id = 0 props.active_pset_id = 0
return {"FINISHED"} return {"FINISHED"}
@@ -161,12 +190,8 @@ class EditPset(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
if self.obj_type == "Object": props = get_pset_props(context, self.obj, self.obj_type)
obj = bpy.data.objects.get(self.obj) ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.PsetProperties
properties = {} properties = {}
pset_id = self.pset_id or props.active_pset_id pset_id = self.pset_id or props.active_pset_id
@@ -213,7 +238,7 @@ class EditPset(bpy.types.Operator):
}, },
) )
CostData.purge() 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) bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
return {"FINISHED"} return {"FINISHED"}
@@ -231,20 +256,17 @@ class RemovePset(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
if self.obj_type == "Object": props = get_pset_props(context, self.obj, self.obj_type)
obj = bpy.data.objects.get(self.obj) ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
props = obj.BIMObjectProperties
ifcopenshell.api.run( ifcopenshell.api.run(
"pset.remove_pset", "pset.remove_pset",
self.file, 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), "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"} return {"FINISHED"}
@@ -254,36 +276,24 @@ class AddPset(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty() obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty() obj_type: bpy.props.StringProperty()
pset_name: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
if self.obj_type == "Object": props = get_pset_props(context, self.obj, self.obj_type)
obj = bpy.data.objects.get(self.obj) ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
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
ifcopenshell.api.run( ifcopenshell.api.run(
"pset.add_pset", "pset.add_pset",
self.file, self.file,
**{ **{
"product": self.file.by_id(oprops.ifc_definition_id), "product": self.file.by_id(ifc_definition_id),
"name": pset_name, "name": props.pset_name,
}, },
) )
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"} return {"FINISHED"}
@@ -291,24 +301,25 @@ class AddQto(bpy.types.Operator):
bl_idname = "bim.add_qto" bl_idname = "bim.add_qto"
bl_label = "Add Qto" bl_label = "Add Qto"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
obj = context.active_object props = get_pset_props(context, self.obj, self.obj_type)
oprops = obj.BIMObjectProperties ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
props = obj.PsetProperties
ifcopenshell.api.run( ifcopenshell.api.run(
"pset.add_qto", "pset.add_qto",
self.file, self.file,
**{ **{
"product": self.file.by_id(oprops.ifc_definition_id), "product": self.file.by_id(ifc_definition_id),
"name": props.qto_name, "name": props.qto_name,
}, },
) )
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"} return {"FINISHED"}
@@ -52,6 +52,36 @@ def getMaterialPsetNames(self, context):
return psetnames[ifc_class] 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): def getQtoNames(self, context):
global qtonames global qtonames
if "/" in context.active_object.name: if "/" in context.active_object.name:
@@ -69,4 +99,31 @@ class PsetProperties(PropertyGroup):
properties: CollectionProperty(name="Properties", type=Attribute) properties: CollectionProperty(name="Properties", type=Attribute)
pset_name: EnumProperty(items=getPsetNames, name="Pset Name") pset_name: EnumProperty(items=getPsetNames, name="Pset Name")
qto_name: EnumProperty(items=getQtoNames, name="Qto 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 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): def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
box = layout.box() box = layout.box()
row = box.row(align=True) 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 pset["is_expanded"] = True
icon = "TRIA_DOWN" if pset["is_expanded"] else "TRIA_RIGHT" 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 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: if not props.active_pset_id:
row.label(text=pset["Name"], icon="COPY_ID") row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="") op = row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="")
op.pset_id = pset_id 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.obj_type = obj_type
op = row.operator("bim.remove_pset", icon="X", text="") op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id 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.obj_type = obj_type
elif props.active_pset_id != pset_id: elif props.active_pset_id != pset_id:
row.label(text=pset["Name"], icon="COPY_ID") row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.remove_pset", icon="X", text="") op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id 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.obj_type = obj_type
elif props.active_pset_id == pset_id: elif props.active_pset_id == pset_id:
row.prop(props, "active_pset_name", icon="COPY_ID", text="") row.prop(props, "active_pset_name", icon="COPY_ID", text="")
op = row.operator("bim.edit_pset", icon="CHECKMARK", 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.obj_type = obj_type
op = row.operator("bim.disable_pset_editing", icon="CANCEL", text="") 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 op.obj_type = obj_type
if pset["is_expanded"]: if pset["is_expanded"]:
if props.active_pset_id == pset_id: if props.active_pset_id == pset_id:
@@ -123,7 +132,7 @@ class BIM_PT_object_psets(Panel):
op.obj_type = "Object" op.obj_type = "Object"
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]] 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") draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Object")
# TODO reimplement. See #1222. # TODO reimplement. See #1222.
@@ -163,10 +172,12 @@ class BIM_PT_object_qtos(Panel):
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "qto_name", text="") 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"]] 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") 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: if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True) 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 = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.active_material.name op.obj = context.active_object.active_material.name
op.obj_type = "Material" op.obj_type = "Material"
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]] 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") 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): def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": 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) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name =="LevelingDelay" or "Work" in prop.name: 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) attributes[prop.name] = helper.parse_duration(prop.string_value)
return True return True
@@ -189,6 +189,11 @@ class AssignClass(bpy.types.Operator):
continue continue
spatial_obj = bpy.data.objects.get(collection.name) spatial_obj = bpy.data.objects.get(collection.name)
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id: 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( bpy.ops.bim.assign_container(
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
) )
@@ -76,9 +76,7 @@ classes = (
operator.AddTaskColumn, operator.AddTaskColumn,
operator.RemoveTaskColumn, operator.RemoveTaskColumn,
operator.SetTaskSortColumn, operator.SetTaskSortColumn,
operator.EnableAssigningResources, operator.EnableAssigningProcessToResource,
operator.AssignResource,
operator.UnassignResource,
prop.WorkPlan, prop.WorkPlan,
prop.BIMWorkPlanProperties, prop.BIMWorkPlanProperties,
prop.Task, prop.Task,
@@ -58,9 +58,15 @@ class EditWorkPlan(bpy.types.Operator):
def export_attributes(self, attributes, prop): def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name: 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) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name == "Duration" or prop.name == "TotalFloat": 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) attributes[prop.name] = helper.parse_duration(prop.string_value)
return True return True
@@ -214,9 +220,15 @@ class EditWorkSchedule(bpy.types.Operator):
def export_attributes(self, attributes, prop): def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name: 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) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name == "Duration" or prop.name == "TotalFloat": 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) attributes[prop.name] = helper.parse_duration(prop.string_value)
return True return True
@@ -564,9 +576,15 @@ class EditTaskTime(bpy.types.Operator):
def export_attributes(self, attributes, prop): def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": 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) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name == "ScheduleDuration": elif prop.name == "ScheduleDuration":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value) attributes[prop.name] = helper.parse_duration(prop.string_value)
return True return True
@@ -800,22 +818,44 @@ class AssignProcess(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty() task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty() related_object: bpy.props.StringProperty()
parent_resource: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
related_objects = ( self.file = IfcStore.get_file()
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects if self.parent_resource:
) resource = ifcopenshell.api.run(
for related_object in related_objects: "resource.add_resource",
self.file = IfcStore.get_file() 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( ifcopenshell.api.run(
"sequence.assign_process", "sequence.assign_process",
self.file, 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) Data.load(self.file)
return {"FINISHED"} return {"FINISHED"}
@@ -826,22 +866,38 @@ class UnassignProcess(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty() task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty() related_object: bpy.props.StringProperty()
resource: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
related_objects = ( self.file = IfcStore.get_file()
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects if self.resource:
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run( ifcopenshell.api.run(
"sequence.unassign_process", "sequence.unassign_process",
self.file, 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), 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) Data.load(self.file)
return {"FINISHED"} return {"FINISHED"}
@@ -1962,8 +2018,8 @@ class SetTaskSortColumn(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class EnableAssigningResources(bpy.types.Operator): class EnableAssigningProcessToResource(bpy.types.Operator):
bl_idname = "bim.enable_assigning_resources" bl_idname = "bim.enable_assigning_process_to_resources"
bl_label = "Enable Assigning Resources To Tasks" bl_label = "Enable Assigning Resources To Tasks"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty() task: bpy.props.IntProperty()
@@ -1973,66 +2029,3 @@ class EnableAssigningResources(bpy.types.Operator):
self.props.active_task_id = self.task self.props.active_task_id = self.task
self.props.editing_task_type = "RESOURCES" self.props.editing_task_type = "RESOURCES"
return {"FINISHED"} 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): def draw_editable_task_resource_ui(self):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(self.props, "resources", text="") row.prop(self.props, "resources", text="")
op = row.operator("bim.assign_resource", text="", icon="ADD") op = row.operator("bim.assign_process", text="", icon="ADD")
op.parent_resource = int(self.props.resources) if self.props.resources:
op.task = self.props.active_task_id op.parent_resource = int(self.props.resources)
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.task = self.props.active_task_id 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): class BIM_UL_task_columns(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -436,7 +435,7 @@ class BIM_UL_tasks(UIList):
row.operator( row.operator(
"bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO" "bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO"
).task = item.ifc_definition_id ).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.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.add_task", text="", icon="ADD").task = item.ifc_definition_id
row.operator("bim.remove_task", text="", icon="X").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.DisableEditingType,
operator.SelectSimilarType, operator.SelectSimilarType,
operator.SelectTypeObjects, operator.SelectTypeObjects,
operator.SelectType,
prop.BIMTypeProperties, prop.BIMTypeProperties,
prop.BIMTypeObjectProperties, prop.BIMTypeObjectProperties,
ui.BIM_PT_type, ui.BIM_PT_type,
@@ -109,6 +109,24 @@ class DisableEditingType(bpy.types.Operator):
return {"FINISHED"} 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): class SelectSimilarType(bpy.types.Operator):
bl_idname = "bim.select_similar_type" bl_idname = "bim.select_similar_type"
bl_label = "Select Similar Type" bl_label = "Select Similar Type"
@@ -64,6 +64,7 @@ class BIM_PT_type(Panel):
else: else:
label = name label = name
row.label(text=label) 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.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="") row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
if name != "None/None": if name != "None/None":
@@ -3,10 +3,12 @@ from . import ui, prop, operator
classes = ( classes = (
operator.AssignUnit, operator.AssignUnit,
operator.UnassignUnit,
operator.LoadUnits, operator.LoadUnits,
operator.DisableUnitEditingUI, operator.DisableUnitEditingUI,
operator.RemoveUnit, operator.RemoveUnit,
operator.AddMonetaryUnit, operator.AddMonetaryUnit,
operator.AddSIUnit,
operator.EnableEditingUnit, operator.EnableEditingUnit,
operator.DisableEditingUnit, operator.DisableEditingUnit,
operator.EditUnit, operator.EditUnit,
@@ -1,5 +1,6 @@
import bpy import bpy
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.unit
import blenderbim.bim.helper import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.unit.data import Data from ifcopenshell.api.unit.data import Data
@@ -9,13 +10,20 @@ class AssignUnit(bpy.types.Operator):
bl_idname = "bim.assign_unit" bl_idname = "bim.assign_unit"
bl_label = "Assign Unit" bl_label = "Assign Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
ifcopenshell.api.run("unit.assign_unit", IfcStore.get_file(), **self.get_units(context)) self.file = IfcStore.get_file()
Data.load(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"} return {"FINISHED"}
def get_units(self, context): def get_units(self, context):
@@ -46,6 +54,23 @@ class AssignUnit(bpy.types.Operator):
return units 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): class LoadUnits(bpy.types.Operator):
bl_idname = "bim.load_units" bl_idname = "bim.load_units"
bl_label = "Load Units" bl_label = "Load Units"
@@ -56,8 +81,7 @@ class LoadUnits(bpy.types.Operator):
while len(props.units) > 0: while len(props.units) > 0:
props.units.remove(0) props.units.remove(0)
for ifc_definition_id in Data.unit_assignment: for ifc_definition_id, unit in Data.units.items():
unit = Data.units[ifc_definition_id]
name = unit.get("Name", "") name = unit.get("Name", "")
if unit["type"] == "IfcMonetaryUnit": if unit["type"] == "IfcMonetaryUnit":
@@ -86,6 +110,7 @@ class LoadUnits(bpy.types.Operator):
new.ifc_definition_id = ifc_definition_id new.ifc_definition_id = ifc_definition_id
new.name = name new.name = name
new.unit_type = unit_type new.unit_type = unit_type
new.is_assigned = ifc_definition_id in Data.unit_assignment
new.icon = icon new.icon = icon
props.is_editing = True props.is_editing = True
@@ -132,8 +157,29 @@ class AddMonetaryUnit(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties props = context.scene.BIMUnitProperties
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
unit = ifcopenshell.api.run("unit.add_monetary_unit", self.file) ifcopenshell.api.run("unit.add_monetary_unit", self.file)
ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit]) 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) Data.load(self.file)
bpy.ops.bim.load_units() bpy.ops.bim.load_units()
return {"FINISHED"} return {"FINISHED"}
@@ -1,6 +1,7 @@
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -17,11 +18,14 @@ from bpy.props import (
unitclasses_enum = [] unitclasses_enum = []
namedunittypes_enum = []
def purge(): def purge():
global unitclasses_enum global unitclasses_enum
global namedunittypes_enum
unitclasses_enum = [] unitclasses_enum = []
namedunittypes_enum = []
def getUnitClasses(self, context): def getUnitClasses(self, context):
@@ -33,9 +37,20 @@ def getUnitClasses(self, context):
return unitclasses_enum 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): class Unit(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
unit_type: StringProperty(name="Unit Type") unit_type: StringProperty(name="Unit Type")
is_assigned: BoolProperty(name="Is Assigned")
icon: StringProperty(name="Icon") icon: StringProperty(name="Icon")
ifc_definition_id: IntProperty(name="IFC Definition ID") 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_index: IntProperty(name="Active Unit Index")
active_unit_id: IntProperty(name="Active Unit Id") active_unit_id: IntProperty(name="Active Unit Id")
unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes") 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) 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") row.operator("bim.add_monetary_unit", text="", icon="ADD")
elif self.props.unit_classes == "IfcDerivedUnit": elif self.props.unit_classes == "IfcDerivedUnit":
pass # TODO 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: else:
pass # TODO row.prop(self.props, "named_unit_types", text="")
self.layout.template_list( self.layout.template_list(
"BIM_UL_units", "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.unit_type or "No Type", icon=item.icon)
row.label(text=item.name or "Unnamed") 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: if props.active_unit_id == item.ifc_definition_id:
row.operator("bim.edit_unit", text="", icon="CHECKMARK") row.operator("bim.edit_unit", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_unit", text="", icon="CANCEL") row.operator("bim.disable_editing_unit", text="", icon="CANCEL")
+1 -1
View File
@@ -30,7 +30,7 @@ class IfcSchema:
self.property_files = [] self.property_files = []
property_paths = self.data_dir.joinpath("pset").glob("*.ifc") property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
# TODO: add IFC2X3 PsetQto template support # 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: for path in property_paths:
self.psetqto.templates.append(ifcopenshell.open(path)) self.psetqto.templates.append(ifcopenshell.open(path))
+2
View File
@@ -58,6 +58,7 @@ class LibraryGenerator:
profile = self.file.create_entity( profile = self.file.create_entity(
"IfcIShapeProfileDef", "IfcIShapeProfileDef",
ProfileName="DEMO-I",
ProfileType="AREA", ProfileType="AREA",
OverallWidth=0.1, OverallWidth=0.1,
OverallDepth=0.2, OverallDepth=0.2,
@@ -69,6 +70,7 @@ class LibraryGenerator:
profile = self.file.create_entity( profile = self.file.create_entity(
"IfcCShapeProfileDef", "IfcCShapeProfileDef",
ProfileName="DEMO-C",
ProfileType="AREA", ProfileType="AREA",
Depth=0.2, Depth=0.2,
Width=0.1, 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 import json
from run import db, app from run import db, app
website_obj = Blueprint( foundation_obj = Blueprint(
"website_obj", "foundation_obj",
__name__, __name__,
template_folder="templates", template_folder="templates",
) )
@@ -22,13 +22,13 @@ def split_by_crlf(s):
return [v for v in s.splitlines() if v] return [v for v in s.splitlines() if v]
@website_obj.route("/") @foundation_obj.route("/")
def homepage(): def homepage():
return render_template("index.html") return render_template("index.html")
# return "homepage" # return "homepage"
@website_obj.route("/register", methods=["GET", "POST"]) @foundation_obj.route("/register", methods=["GET", "POST"])
def register_page(): def register_page():
form = RegisterForm() form = RegisterForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -45,7 +45,7 @@ def register_page():
f"Account created successfully! {user_to_create.username}", f"Account created successfully! {user_to_create.username}",
category="success", category="success",
) )
return redirect(url_for("website_obj.homepage")) return redirect(url_for("foundation_obj.homepage"))
if form.errors != {}: if form.errors != {}:
for err_msg in form.errors.values(): for err_msg in form.errors.values():
flash( flash(
@@ -55,7 +55,7 @@ def register_page():
return render_template("register.html", form=form) return render_template("register.html", form=form)
@website_obj.route("/createclient", methods=["GET", "POST"]) @foundation_obj.route("/createclient", methods=["GET", "POST"])
@login_required @login_required
def create_client(): def create_client():
grants = [ grants = [
@@ -99,7 +99,7 @@ def create_client():
return render_template("createclient.html", form=form, grants=grants) 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(): def login_page():
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -108,7 +108,7 @@ def login_page():
attempted_password=form.password.data attempted_password=form.password.data
): ):
login_user(attempted_user) login_user(attempted_user)
return redirect(url_for("website_obj.homepage")) return redirect(url_for("foundation_obj.homepage"))
else: else:
flash( flash(
"Invalid Credentials", "Invalid Credentials",
@@ -118,14 +118,14 @@ def login_page():
return render_template("login.html", form=form) return render_template("login.html", form=form)
@website_obj.route("/logout") @foundation_obj.route("/logout")
def logoutpage(): def logoutpage():
logout_user() logout_user()
flash("You have been logged out!", category="info") 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(): def foundation_auth():
data = { data = {
"oauth2_auth_url": "http://127.0.0.1:5000/oauth/authorize", "oauth2_auth_url": "http://127.0.0.1:5000/oauth/authorize",
@@ -138,7 +138,7 @@ def foundation_auth():
return response return response
@website_obj.route("/foundation/versions") @foundation_obj.route("/foundation/versions")
def foundation_versions(): def foundation_versions():
Body = { Body = {
"versions": [ "versions": [
@@ -167,7 +167,7 @@ def foundation_versions():
return response return response
@website_obj.route("/outh/login", methods=["GET", "POST"]) @foundation_obj.route("/outh/login", methods=["GET", "POST"])
def oauth_login(): def oauth_login():
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -185,7 +185,7 @@ def oauth_login():
state = request.args.get("state") state = request.args.get("state")
return redirect( return redirect(
url_for( url_for(
"website_obj.authorize", "foundation_obj.authorize",
client_id=client_id, client_id=client_id,
redirect_uri=redirect_uri, redirect_uri=redirect_uri,
state=state, state=state,
@@ -199,7 +199,7 @@ def oauth_login():
return render_template("ologin.html", form=form) 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(): def authorize():
client_id = request.args.get("client_id") client_id = request.args.get("client_id")
redirect_uri = request.args.get("redirect_uri") redirect_uri = request.args.get("redirect_uri")
@@ -209,7 +209,7 @@ def authorize():
query = request.query_string query = request.query_string
return redirect( return redirect(
url_for( url_for(
"website_obj.oauth_login", "foundation_obj.oauth_login",
client_id=client_id, client_id=client_id,
redirect_uri=redirect_uri, redirect_uri=redirect_uri,
state=state, state=state,
@@ -252,7 +252,7 @@ def authorize():
return render_template("oauth.html") return render_template("oauth.html")
@website_obj.route("/oauth/token", methods=["POST"]) @foundation_obj.route("/oauth/token", methods=["POST"])
def issue_token(): def issue_token():
try: try:
code = request.form["code"] code = request.form["code"]
@@ -30,7 +30,7 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item active"> <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 >Home <span class="sr-only">(current)</span></a
> >
</li> </li>
@@ -47,14 +47,16 @@
<a class="nav-link">Welcome, {{ current_user.username }}</a> <a class="nav-link">Welcome, {{ current_user.username }}</a>
</li> </li>
<li class="nav-item"> <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 >Logout</a
> >
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link" class="nav-link"
href="{{ url_for('website_obj.create_client') }}" href="{{ url_for('foundation_obj.create_client') }}"
>Create Client</a >Create Client</a
> >
</li> </li>
@@ -62,14 +64,16 @@
{% else %} {% else %}
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <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 >Login</a
> >
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link" class="nav-link"
href="{{ url_for('website_obj.register_page') }}" href="{{ url_for('foundation_obj.register_page') }}"
>Register</a >Register</a
> >
</li> </li>
@@ -15,7 +15,7 @@
<h6>Do not have an account?</h6> <h6>Do not have an account?</h6>
<a <a
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.register_page') }}" href="{{ url_for('foundation_obj.register_page') }}"
>Register</a >Register</a
> >
</div> </div>
@@ -16,7 +16,7 @@
<h6>Do not have an account?</h6> <h6>Do not have an account?</h6>
<a <a
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.register_page') }}" href="{{ url_for('foundation_obj.register_page') }}"
>Register</a >Register</a
> >
</div> </div>
@@ -18,7 +18,7 @@
<h6>Already have an account?</h6> <h6>Already have an account?</h6>
<a <a
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
href="{{ url_for('website_obj.login_page') }}" href="{{ url_for('foundation_obj.login_page') }}"
>Login</a >Login</a
> >
</div> </div>
@@ -10,14 +10,14 @@ login_manager = LoginManager(app)
bcrypt = Bcrypt(app) bcrypt = Bcrypt(app)
app.config["SECRET_KEY"] = "f613729206685405cde0e388" app.config["SECRET_KEY"] = "f613729206685405cde0e388"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db" 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" login_manager.login_message_category = "info"
from website.routes import website_obj from foundation.routes import foundation_obj
from bcf.routes import bcf from bcf.routes import bcf
app.register_blueprint(website_obj) app.register_blueprint(foundation_obj)
app.register_blueprint(bcf) app.register_blueprint(bcf)
+4 -1
View File
@@ -60,18 +60,20 @@ class Csv2Ifc:
def get_row_cost_data(self, row): def get_row_cost_data(self, row):
name = row[self.headers["Name"]] 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 = row[self.headers["Quantity"]]
cost_quantities_unit = row[self.headers["Unit"]] cost_quantities_unit = row[self.headers["Unit"]]
if self.has_categories: if self.has_categories:
cost_values = { cost_values = {
k: float(row[v]) k: float(row[v])
for k, v in self.headers.items() 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: else:
cost_values = row[self.headers["Value"]] cost_values = row[self.headers["Value"]]
cost_values = float(cost_values) if cost_values else None cost_values = float(cost_values) if cost_values else None
return { return {
"Identification": str(identification) if identification else None,
"Name": str(name) if name else None, "Name": str(name) if name else None,
"CostQuantities": float(cost_quantities) if cost_quantities else None, "CostQuantities": float(cost_quantities) if cost_quantities else None,
"CostQuantitiesUnit": str(cost_quantities_unit) if cost_quantities_unit 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"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent)
cost_item["ifc"].Name = cost_item["Name"] cost_item["ifc"].Name = cost_item["Name"]
cost_item["ifc"].Identification = cost_item["Identification"]
if not cost_item["CostValues"]: if not cost_item["CostValues"]:
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) 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" "Hierarchy","Identification","Name","Quantity","Unit","Foo","Bar","Baz","Subtotal"
1,"Demolition",,,,,,301 1,1,"Demolition",,,,,,301
,,,,,,, ,,,,,,,,
2,"Building A",,,,,,96 2,1.1,"Building A",,,,,,96
,,,,,,, ,,,,,,,,
3,"Soft strip",1,"m",5,4,3,12 3,"1.1.1","Soft strip",1,"m",5,4,3,12
3,"Hard Strip",2,"m2",6,5,4,30 3,"1.1.2","Hard Strip",2,"m2",6,5,4,30
3,"Hazmat",3,"m3",7,6,5,54 3,"1.1.3","Hazmat",3,"m3",7,6,5,54
,,,,,,, ,,,,,,,,
2,"Building B",,,,,,205 2,1.2,"Building B",,,,,,205
,,,,,,, ,,,,,,,,
3,"Soft strip",4,"hr",8,7,,60 3,"1.2.1","Soft strip",4,"hr",8,7,,60
3,"Hard Strip",5,,9,,8,85 3,"1.2.2","Hard Strip",5,,9,,8,85
3,"Hazmat",6,"kg",10,,,60 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 = { JSON_TO_IFC = {
"Building": ["IfcBuilding"], "Building": ["IfcBuilding"],
"BuildingPart": ["IfcBuilding", {"CompositionType": "Partial"}], # CompositionType: Partial "BuildingPart": ["IfcBuilding", {"CompositionType": "PARTIAL"}],
"BuildingInstallation": ["IfcDistributionElement"], "BuildingInstallation": ["IfcBuildingElementProxy"],
"Road": ["IfcCivilElement"], "Road": ["IfcCivilElement"], # Update for IFC4.3
"TransportSquare": ["IfcSpace"], "Railway": ["IfcCivilElement"], # Update for IFC4.3
"TINRelief": ["IfcGeographicElement"], "TransportSquare": ["IfcCivilElement"], # Update for IFC4.3
"WaterBody": ["IfcGeographicElement"], # Update for IFC4.3 "TINRelief": ["IfcGeographicElement", {"PredefinedType": "TERRAIN"}],
"LandUse": ["IfcGeographicElement"], "WaterBody": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"PlantCover": ["IfcGeographicElement"], "ObjectType": "WaterBody"}], # Update for IFC4.3
"SolitaryVegetationObject": ["IfcGeographicElement"], "LandUse": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "LandUse"}],
"PlantCover": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "Plantcover"}],
"SolitaryVegetationObject": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"ObjectType": "SolitaryVegetationObject"}],
"CityFurniture": ["IfcFurnishingElement"], "CityFurniture": ["IfcFurnishingElement"],
"GenericCityObject": ["IfcCivilElement"], "GenericCityObject": ["IfcCivilElement"],
"Bridge": ["IfcCivilElement"], # Update for IFC4.3 "Bridge": ["IfcCivilElement"], # Update for IFC4.3
@@ -42,7 +47,7 @@ JSON_TO_IFC = {
"Tunnel": ["IfcCivilElement"], # Update for IFC4.3 "Tunnel": ["IfcCivilElement"], # Update for IFC4.3
"TunnelPart": ["IfcCivilElement"], # Update for IFC4.3 "TunnelPart": ["IfcCivilElement"], # Update for IFC4.3
"TunnelInstallation": ["IfcCivilElement"], # Update for IFC4.3 "TunnelInstallation": ["IfcCivilElement"], # Update for IFC4.3
"CityObjectGroup": ["IfcCivilElement"], "CityObjectGroup": ["IfcBuilding"], # Update for IFC4.3
"GroundSurface": ["IfcSlab", {"PredefinedType": "BASESLAB"}], "GroundSurface": ["IfcSlab", {"PredefinedType": "BASESLAB"}],
"RoofSurface": ["IfcRoof"], "RoofSurface": ["IfcRoof"],
"WallSurface": ["IfcWall"], "WallSurface": ["IfcWall"],
@@ -51,9 +56,12 @@ JSON_TO_IFC = {
"OuterFloorSurface": ["IfcSlab", {"PredefinedType": "FLOOR"}], "OuterFloorSurface": ["IfcSlab", {"PredefinedType": "FLOOR"}],
"Window": ["IfcWindow"], "Window": ["IfcWindow"],
"Door": ["IfcDoor"], "Door": ["IfcDoor"],
"WaterSurface": ["IfcGeographicElement"], # Update for IFC4.3 "WaterSurface": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED",
"WaterGroundSurface": ["IfcGeographicElement"], # Update for IFC4.3 "ObjectType": "WaterSurface"}], # Update for IFC4.3
"WaterClosureSurface": ["IfcGeographicElement"], # 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 "TrafficArea": ["IfcCivilElement"], # Update for IFC4.3
"AuxiliaryTrafficArea": ["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"]) self.IFC_model.write(self.properties["file_destination"])
def create_IFC_classes(self): 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(): for obj_id, obj in self.city_model.get_cityobjects().items():
# CityJSON type to class # CityJSON type to class
@@ -153,22 +162,19 @@ class Cityjson2ifc:
if "name_attribute" in self.properties and self.properties["name_attribute"] in obj.attributes: if "name_attribute" in self.properties and self.properties["name_attribute"] in obj.attributes:
IFC_name = obj.attributes[self.properties["name_attribute"]] IFC_name = obj.attributes[self.properties["name_attribute"]]
# TODO children
# TODO parents
# TODO geometry_type
# geometry_lod # geometry_lod
lod = 0 lod = 0
geometry = None geometry = None
if len(obj.geometry) == 0:
print(f"Warning: Object {obj_id} has no geometry.")
for geom in obj.geometry: for geom in obj.geometry:
if geom.lod > lod: if geom.lod > lod:
geometry = geom geometry = geom
lod = geom.lod lod = geom.lod
IFC_children = [] IFC_semantic_surface_children = []
if geometry.surfaces: if geometry and geometry.surfaces:
for surface_id in geometry.surfaces: for surface_id in geometry.surfaces:
IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0] IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0]
child_data = {"GlobalId": ifcopenshell.guid.new(), 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) surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id)
if surface_geometry: if surface_geometry:
child_data["Representation"] = self.create_IFC_representation(surface_geometry, 'brep') 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) IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, geometry)
if IFC_geometry: if IFC_geometry:
data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type) data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type)
@@ -189,20 +195,36 @@ class Cityjson2ifc:
data["Name"] = IFC_name data["Name"] = IFC_name
IFC_object = self.IFC_model.create_entity(IFC_class, **data) IFC_object = self.IFC_model.create_entity(IFC_class, **data)
# Define aggregation # Define aggregation
self.IFC_model.create_entity("IfcRelContainedInSpatialStructure", if len(obj.parents) == 0:
**{"GlobalId": ifcopenshell.guid.new(), parents_children_relations["IfcSite"]['Children'].append(IFC_object)
"RelatedElements": [IFC_object],
"RelatingStructure": self.IFC_site} for parent in obj.parents:
) if parent not in parents_children_relations:
if IFC_children: 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", self.IFC_model.create_entity("IfcRelAggregates",
**{"GlobalId": ifcopenshell.guid.new(), **{"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": IFC_children, "RelatedObjects": IFC_semantic_surface_children,
"RelatingObject": IFC_object}) "RelatingObject": IFC_object})
self.create_property_set(obj.attributes, 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): def create_IFC_representation(self, IFC_geometry, shape_representation_type):
if not isinstance(IFC_geometry, list): if not isinstance(IFC_geometry, list):
IFC_geometry = [IFC_geometry] 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) { if (shape_type(l) != ST_SHAPELIST) {
TopoDS_Shape shp; TopoDS_Shape shp;
if (convert_shape(l, 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 true;
} }
return false; 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: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {"cost_item": None, "prop_name": ""} self.settings = {"cost_item": None, "products": [], "prop_name": ""}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
self.quantities = set(self.settings["cost_item"].CostQuantities or []) self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for control in self.settings["cost_item"].Controls or []: for product in self.settings["products"]:
for related_object in control.RelatedObjects: ifcopenshell.api.run(
self.add_quantity_from_related_object(related_object) "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) self.settings["cost_item"].CostQuantities = list(self.quantities)
def add_quantity_from_related_object(self, element): def add_quantity_from_related_object(self, element):
if element.is_a("IfcTypeObject"): if not element.is_a("IfcObject"):
for definition in element.HasPropertySets or []: return
self.add_quantity_from_qto(definition) for relationship in element.IsDefinedBy:
else: if relationship.is_a("IfcRelDefinesByProperties"):
for relationship in element.IsDefinedBy: self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
def add_quantity_from_qto(self, qto): def add_quantity_from_qto(self, qto):
if not qto.is_a("IfcElementQuantity"): if not qto.is_a("IfcElementQuantity"):
@@ -51,21 +51,43 @@ class Data:
del data["OwnerHistory"] del data["OwnerHistory"]
del data["CostValues"] del data["CostValues"]
data["IsNestedBy"] = [] data["IsNestedBy"] = []
data["Controls"] = [] data["Controls"] = {}
for rel in cost_item.IsNestedBy: for rel in cost_item.IsNestedBy:
[data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")] [data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
parametric_quantities = []
for rel in cost_item.Controls: 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.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.load_cost_item_values(cost_item, data)
cls.is_loaded = True cls.is_loaded = True
@classmethod @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["CostQuantities"] = []
data["TotalCostQuantity"] = cls.get_total_quantity(cost_item) data["TotalCostQuantity"] = cls.get_total_quantity(cost_item)
for quantity in cost_item.CostQuantities or []: for quantity in cost_item.CostQuantities or []:
if quantity.id() in parametric_quantities:
continue
quantity_data = quantity.get_info() quantity_data = quantity.get_info()
del quantity_data["Unit"] del quantity_data["Unit"]
cls.physical_quantities[quantity.id()] = quantity_data 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): def load_cost_item_value(cls, cost_item_data, cost_item, cost_value):
value_data = cost_value.get_info() value_data = cost_value.get_info()
del value_data["AppliedValue"] 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"]: if value_data["ApplicableDate"]:
value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"]) value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
if value_data["FixedUntilDate"]: if value_data["FixedUntilDate"]:
@@ -1,3 +1,8 @@
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -10,4 +15,19 @@ class Usecase:
if name == "AppliedValue" and value is not None: if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types # TODO: support all applied value select types
value = self.file.createIfcMonetaryMeasure(value) 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) 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 []) layers = list(self.settings["layer_set"].MaterialLayers or [])
layer = self.file.create_entity("IfcMaterialLayer", **{ layer = self.file.create_entity("IfcMaterialLayer", **{
"Material": self.settings["material"], "Material": self.settings["material"],
"LayerThickness": 0. "LayerThickness": 1.
}) })
layers.append(layer) layers.append(layer)
self.settings["layer_set"].MaterialLayers = layers self.settings["layer_set"].MaterialLayers = layers
@@ -11,6 +11,7 @@ class Usecase():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
# TODO: don't also edit the profile def in this usecase
for name, value in self.settings["attributes"].items(): for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value) setattr(self.settings["profile"], name, value)
self.settings["profile"].Material = self.settings["material"] self.settings["profile"].Material = self.settings["material"]
@@ -7,3 +7,4 @@ class Usecase:
def execute(self): def execute(self):
self.file.remove(self.settings["profile"]) 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): def execute(self):
self.added_elements = set() self.added_elements = set()
self.whitelisted_inverse_attributes = {}
if self.settings["element"].is_a("IfcTypeProduct"): if self.settings["element"].is_a("IfcTypeProduct"):
return self.append_type_product() return self.append_type_product()
elif self.settings["element"].is_a("IfcMaterial"): elif self.settings["element"].is_a("IfcMaterial"):
return self.append_material() return self.append_material()
elif self.settings["element"].is_a("IfcCostSchedule"): elif self.settings["element"].is_a("IfcCostSchedule"):
return self.append_cost_schedule() return self.append_cost_schedule()
elif self.settings["element"].is_a("IfcProfileDef"):
return self.append_profile_def()
def is_already_appended(self): def is_already_appended(self):
try: try:
@@ -36,6 +39,12 @@ class Usecase:
self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]} self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]}
return self.add_element(self.settings["element"]) 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): def append_type_product(self):
if self.is_already_appended(): if self.is_already_appended():
return return
@@ -64,3 +64,15 @@ class Usecase:
"Material": self.settings["product"], "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) cls.add_type_product_psets(product, product_id)
elif product.is_a("IfcMaterialDefinition"): elif product.is_a("IfcMaterialDefinition"):
cls.add_material_psets(product, product_id) cls.add_material_psets(product, product_id)
elif product.is_a("IfcProfileDef"):
cls.add_profile_psets(product, product_id)
else: else:
cls.add_product_psets(product, product_id) cls.add_product_psets(product, product_id)
@@ -44,6 +46,13 @@ class Data:
for pset in product.HasProperties: for pset in product.HasProperties:
cls.add_pset(pset, product_id) 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 @classmethod
def add_product_psets(cls, product, product_id): def add_product_psets(cls, product, product_id):
if not hasattr(product, "IsDefinedBy") or not product.IsDefinedBy: if not hasattr(product, "IsDefinedBy") or not product.IsDefinedBy:
@@ -59,7 +68,7 @@ class Data:
@classmethod @classmethod
def add_pset(cls, pset, product_id): def add_pset(cls, pset, product_id):
data = pset.get_info() 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["OwnerHistory"]
del data["HasProperties"] del data["HasProperties"]
if hasattr(pset, "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 return
for unit in unit_assignment[0].Units: for unit in unit_assignment[0].Units:
cls.unit_assignment.append(unit.id()) 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.load_unit(unit)
cls.is_loaded = True 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(type)]
return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(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 """Get a list of all referenced instances for a particular instance including itself
:param inst: The entity instance to get all sub instances :param inst: The entity instance to get all sub instances
:type inst: ifcopenshell.entity_instance.entity_instance :type inst: ifcopenshell.entity_instance.entity_instance
:param max_levels: How far deep to recursively fetch sub instances. None or -1 means infinite. :param max_levels: How far deep to recursively fetch sub instances. None or -1 means infinite.
:type max_levels: None|int :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 :returns: A list of ifcopenshell.entity_instance.entity_instance objects
:rtype: list :rtype: list
""" """
if max_levels is None: if max_levels is None:
max_levels = -1 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): def get_inverse(self, inst):
"""Return a list of entities that reference this entity """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): def remove_deep(ifc_file, element):
# @todo maybe some sort of try-finally mechanism. # @todo maybe some sort of try-finally mechanism.
ifc_file.batch() ifc_file.batch()
subgraph = list(ifc_file.traverse(element)) subgraph = list(ifc_file.traverse(element, breadth_first=True))
subgraph_set = set(subgraph) subgraph_set = set(subgraph)
for ref in subgraph[::-1]: for ref in subgraph[::-1]:
if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0: if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0:
@@ -25,7 +25,7 @@ unit_names = [
"CANDELA", "CANDELA",
"COULOMB", "COULOMB",
"CUBIC_METRE", "CUBIC_METRE",
"DEGREE CELSIUS", "DEGREE_CELSIUS",
"FARAD", "FARAD",
"GRAM", "GRAM",
"GRAY", "GRAY",
@@ -43,7 +43,7 @@ unit_names = [
"SECOND", "SECOND",
"SIEMENS", "SIEMENS",
"SIEVERT", "SIEVERT",
"SQUARE METRE", "SQUARE_METRE",
"METRE", "METRE",
"STERADIAN", "STERADIAN",
"TESLA", "TESLA",
@@ -52,7 +52,6 @@ unit_names = [
"WEBER", "WEBER",
] ]
si_dimensions = { si_dimensions = {
"METRE": (1, 0, 0, 0, 0, 0, 0), "METRE": (1, 0, 0, 0, 0, 0, 0),
"SQUARE_METRE": (2, 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), "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 = { si_conversions = {
"inch": 0.0254, "inch": 0.0254,
"foot": 0.3048, "foot": 0.3048,
@@ -169,7 +234,7 @@ def get_prefix_multiplier(text):
def get_unit_name(text): def get_unit_name(text):
text = text.upper().replace("METER", "METRE") text = text.upper().replace("METER", "METRE")
for name in unit_names: for name in unit_names:
if name in text: if name.replace("_", " ") in text:
return name return name
@@ -177,6 +242,10 @@ def get_si_dimensions(name):
return si_dimensions.get(name, si_dimensions["OTHERWISE"]) 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): def get_property_unit(prop, ifc_file):
unit = getattr(prop, "Unit", None) unit = getattr(prop, "Unit", None)
if unit: if unit:
@@ -197,6 +266,10 @@ def get_property_unit(prop, ifc_file):
return units[0] 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): def get_symbol_quantity_class(symbol):
# Dumb, but everybody gets it, unlike regex golf # Dumb, but everybody gets it, unlike regex golf
if not symbol: if not symbol:
@@ -259,19 +332,17 @@ def convert(value, from_prefix, from_unit, to_prefix, to_unit):
return value 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): 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] units = file.by_type("IfcUnitAssignment")[0]
unit_scale = 1 unit_scale = 1
for unit in units.Units: for unit in units.Units:
@@ -34,7 +34,9 @@ class json_logger:
self.instance = instance self.instance = instance
def log(self, level, message, *args, **kwargs): 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): def __getattr__(self, level):
return functools.partial(self.log, level, instance=self.instance) 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): if isinstance(attr_type, simple_type):
invalid = type(val) != simple_type_python_mapping[attr_type.declared_type()] invalid = type(val) != simple_type_python_mapping[attr_type.declared_type()]
elif isinstance(attr_type, (entity_type, type_declaration)): 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): elif isinstance(attr_type, select_type):
val_to_use = val val_to_use = val
if isinstance(schema.declaration_by_name(val.is_a()), enumeration_type): if isinstance(schema.declaration_by_name(val.is_a()), enumeration_type):
@@ -90,13 +94,19 @@ def assert_valid(attr, val, schema):
else: else:
invalid = True invalid = True
if not invalid: 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): elif isinstance(attr_type, enumeration_type):
invalid = val not in attr_type.enumeration_items() invalid = val not in attr_type.enumeration_items()
elif isinstance(attr_type, aggregation_type): elif isinstance(attr_type, aggregation_type):
b1, b2 = attr_type.bound1(), attr_type.bound2() b1, b2 = attr_type.bound1(), attr_type.bound2()
ty = attr_type.type_of_element() 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: else:
raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type)) raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type))
@@ -135,6 +145,7 @@ def validate(f, logger):
logger.set_instance(inst) logger.set_instance(inst)
entity = schema.declaration_by_name(inst.is_a()) entity = schema.declaration_by_name(inst.is_a())
attrs = entity.all_attributes()
if entity.is_abstract(): if entity.is_abstract():
e = "Entity %s is abstract" % entity.name() e = "Entity %s is abstract" % entity.name()
@@ -143,20 +154,38 @@ def validate(f, logger):
else: else:
logger.error("In %s\n%s", inst, e) 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()): if not has_invalid_value:
logger.error("Attribute %s.%s not optional", entity, attr) for attr, val, is_derived in zip(attrs, inst, entity.derived()):
if val is not None: if val is None and not (is_derived or attr.optional()):
attr_type = attr.type_of_attribute() logger.error("Attribute %s.%s not optional", entity, attr)
try:
assert_valid(attr, val, schema) if val is not None:
except ValidationError as e: attr_type = attr.type_of_attribute()
if hasattr(logger, "set_instance"): try:
logger.error(str(e)) assert_valid(attr, val, schema)
else: except ValidationError as e:
logger.error("In %s\n%s", inst, 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(): for attr in entity.all_inverse_attributes():
val = getattr(inst, attr.name()) val = getattr(inst, attr.name())
+18 -1
View File
@@ -24,6 +24,10 @@
#include <set> #include <set>
#include <iterator> #include <iterator>
#include <boost/unordered_map.hpp> #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" #include "ifc_parse_api.h"
@@ -132,7 +136,16 @@ private:
void build_inverses_(IfcUtil::IfcBaseClass*); 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; bool batch_mode_ = false;
void process_deletion_(); void process_deletion_();
@@ -220,6 +233,10 @@ public:
/// in the first function argument. /// in the first function argument.
aggregate_of_instance::ptr traverse(IfcUtil::IfcBaseClass* instance, int max_level=-1); 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); 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. /// 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; 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 { class traversal_visitor {
private: private:
std::set<IfcUtil::IfcBaseClass*>& visited_; std::set<IfcUtil::IfcBaseClass*>& visited_;
aggregate_of_instance::ptr& list_; traversal_recorder& list_;
int level_; int level_;
int max_level_; int max_level_;
public: 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) : visited_(visited)
, list_(list) , list_(list)
, level_(level) , level_(level)
@@ -1556,12 +1593,12 @@ public:
void operator()(IfcUtil::IfcBaseClass* inst); 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()) { if (visited.find(instance) != visited.end()) {
return; return;
} }
visited.insert(instance); visited.insert(instance);
list->push(instance); list.push_back(level, instance);
if (level >= max_level && max_level > 0) return; 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) { aggregate_of_instance::ptr IfcParse::traverse(IfcUtil::IfcBaseClass* instance, int max_level) {
std::set<IfcUtil::IfcBaseClass*> visited; std::set<IfcUtil::IfcBaseClass*> visited;
aggregate_of_instance::ptr return_value(new aggregate_of_instance); traversal_recorder r(0);
traverse_(instance, visited, return_value, 0, max_level); traverse_(instance, visited, r, 0, max_level);
return return_value; 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 /// @note: for backwards compatibility
@@ -1585,6 +1631,11 @@ aggregate_of_instance::ptr IfcFile::traverse(IfcUtil::IfcBaseClass* instance, in
return IfcParse::traverse(instance, max_level); 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*/) void IfcFile::mark_entity_as_modified(int /*id*/)
{ {
by_ref_cached_.clear(); by_ref_cached_.clear();
@@ -1857,7 +1908,7 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
throw IfcParse::IfcException("Instance not part of this file"); throw IfcParse::IfcException("Instance not part of this file");
} }
batch_deletion_ids_.insert(id); batch_deletion_ids_.push_back(id);
if (!batch_mode_) { if (!batch_mode_) {
process_deletion_(); process_deletion_();
@@ -1866,7 +1917,7 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
void IfcFile::process_deletion_() { void IfcFile::process_deletion_() {
for (auto& id : batch_deletion_ids_) { for (auto& id : batch_deletion_ids_.get<0>()) {
auto entity = instance_by_id(id); auto entity = instance_by_id(id);
aggregate_of_instance::ptr references = instances_by_reference(id); aggregate_of_instance::ptr references = instances_by_reference(id);
@@ -2012,10 +2063,10 @@ void IfcFile::process_deletion_() {
if (batch_mode_) { if (batch_mode_) {
for (auto it = byref.begin(); it != byref.end();) { 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) { if (!do_delete) {
it->second.erase(std::remove_if(it->second.begin(), it->second.end(), [this](int x) { 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()); }), it->second.end());
do_delete = it->second.empty(); 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 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(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); IFC_PARSE_API std::ostream& operator<< (std::ostream& os, const IfcParse::IfcFile& f);
@@ -29,6 +29,8 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
if self.file.schema == "IFC2X3":
return
curve_map = {} curve_map = {}
for curve in self.file.by_type("IfcIndexedPolyCurve"): for curve in self.file.by_type("IfcIndexedPolyCurve"):
+5 -1
View File
@@ -421,7 +421,11 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
} }
} else { } else {
if (!representation) { 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::IfcRepresentationShapeItems shapes = kernel.convert(instance);
IfcGeom::ElementSettings element_settings(settings, kernel.getValue(IfcGeom::Kernel::GV_LENGTH_UNIT), instance->declaration().name()); IfcGeom::ElementSettings element_settings(settings, kernel.getValue(IfcGeom::Kernel::GV_LENGTH_UNIT), instance->declaration().name());