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