mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 16:01:36 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f835ca739 | |||
| 87442f549f | |||
| bf27e41275 | |||
| 66cf06c990 | |||
| 5757a62b6f | |||
| e2552e3d46 | |||
| 1b91540c96 | |||
| 274b12e5a0 | |||
| 496ba6f22d | |||
| b7737cb6d3 | |||
| 55026ca996 | |||
| 3fa573e3c1 | |||
| 00ae6809a9 | |||
| c3fbb11093 | |||
| a0d27c3f1c | |||
| 811a908cc3 | |||
| 94e5baf48a | |||
| cd2570cdbb | |||
| 6df6553935 | |||
| 8e6c372a4f | |||
| 371e11de9b | |||
| d2a708fcc4 | |||
| 845d772a08 | |||
| 1e630ede08 | |||
| c50d1ea2fe | |||
| e7eb00eaa3 | |||
| cd6eac3c8f | |||
| 69e21da60c | |||
| 57b905862a | |||
| 51d3a2b77b | |||
| b2920996be | |||
| bdc5a7a01b | |||
| 1c6a9e2c49 | |||
| 8ffb77551f | |||
| 2d35869c41 | |||
| 359dab573b | |||
| 6e2edbf101 | |||
| 4c8f93d737 | |||
| 2789bc565a | |||
| a1e0b8858e | |||
| f075a6178b | |||
| 5fa385e1e0 | |||
| b57141a80c | |||
| d453adb7cf | |||
| c5a7a8680e | |||
| d4ccadb832 | |||
| cbf04546b8 | |||
| 37c0084874 | |||
| 875afd69d5 | |||
| 67f4ed00d0 | |||
| 10d30b0de7 | |||
| 984b1212a6 | |||
| bc72c927c1 | |||
| b8275f2802 | |||
| 267527f3fc | |||
| c5b4513f65 | |||
| 5da4fbb39c | |||
| 93639e9e50 | |||
| d76462ca42 | |||
| 722201a1af | |||
| 0a3dddef2f | |||
| 26434f0331 | |||
| f6c2e2c20d | |||
| 424a06f6c8 | |||
| 89c4cbeb05 | |||
| 7e13ed746e | |||
| 28d205b4a3 | |||
| dcc52fc4e4 | |||
| a9d785c28f | |||
| 54a3730c20 | |||
| 31d322a28c | |||
| 3b90086109 | |||
| 30b870b98d | |||
| b3e7975b83 | |||
| 8f7f6223de | |||
| 6c313dd25c | |||
| b718bd19bd | |||
| ab5ea4c853 | |||
| d11ec67129 | |||
| 10f894e2ea | |||
| 34bbc8f1ea | |||
| a213ab6760 | |||
| d694d8fcc2 | |||
| 5f2d451c5e |
@@ -151,8 +151,10 @@ endif
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.7.0.zip
|
||||
cd dist/working && unzip v0.7.0.zip
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
|
||||
@@ -18,7 +18,13 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import bpy
|
||||
import platform
|
||||
import traceback
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import addon_utils
|
||||
from collections import deque
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
@@ -32,16 +38,150 @@ bl_info = {
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
last_error = None
|
||||
last_actions: deque = deque(maxlen=10)
|
||||
|
||||
|
||||
def get_debug_info():
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
return {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"last_actions": last_actions,
|
||||
"last_error": last_error,
|
||||
}
|
||||
|
||||
|
||||
def format_debug_info(info: dict):
|
||||
last_actions = ""
|
||||
for action in info["last_actions"]:
|
||||
last_actions += f"\n# {action['type']}: {action['name']}"
|
||||
if settings := action.get("settings"):
|
||||
last_actions += f"\n>>> {settings}"
|
||||
info["last_actions"] = last_actions
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
return text.strip()
|
||||
|
||||
|
||||
if sys.modules.get("bpy", None):
|
||||
# Process *.pth in /libs/site/packages to setup globally importable modules
|
||||
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
|
||||
# site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
|
||||
import blenderbim.bim
|
||||
try:
|
||||
import blenderbim.bim
|
||||
import ifcopenshell.api
|
||||
|
||||
def register():
|
||||
blenderbim.bim.register()
|
||||
def log_api(usecase_path, ifc_file, settings):
|
||||
last_actions.append(
|
||||
{
|
||||
"type": "ifcopenshell.api",
|
||||
"name": usecase_path,
|
||||
"settings": ifcopenshell.api.serialise_settings(settings),
|
||||
}
|
||||
)
|
||||
|
||||
def unregister():
|
||||
blenderbim.bim.unregister()
|
||||
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
|
||||
|
||||
def register():
|
||||
blenderbim.bim.register()
|
||||
|
||||
def unregister():
|
||||
blenderbim.bim.unregister()
|
||||
|
||||
except:
|
||||
last_error = traceback.format_exc()
|
||||
|
||||
print(last_error)
|
||||
print(format_debug_info(get_debug_info()))
|
||||
print("\nFATAL ERROR: Unable to load the BlenderBIM Add-on")
|
||||
|
||||
class BIM_PT_fatal_error(bpy.types.Panel):
|
||||
bl_label = "BlenderBIM Fatal Error"
|
||||
bl_idname = "SCENE_PT_error_message"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="BlenderBIM could not load.", icon="ERROR")
|
||||
layout.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box = layout.box()
|
||||
info = get_debug_info()
|
||||
py = ".".join(info["python_version"].split(".")[0:2])
|
||||
b3d = ".".join(info["blender_version"].split(".")[0:2])
|
||||
box.label(text=f"Blender {b3d} {info['os']} {info['machine']}", icon="BLENDER")
|
||||
box.label(text=f"Python {py} BBIM {info['blenderbim_version']}", icon="SCRIPTPLUGINS")
|
||||
layout.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#installation-issues"
|
||||
|
||||
class OpenUri(bpy.types.Operator):
|
||||
bl_idname = "bim.open_uri"
|
||||
bl_label = "Open URI"
|
||||
uri: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
webbrowser.open(self.uri)
|
||||
return {"FINISHED"}
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_debug_information"
|
||||
bl_label = "Copy Debug Information"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
def execute(self, context):
|
||||
info = format_debug_info(get_debug_info())
|
||||
|
||||
if platform.system() == "Windows":
|
||||
command = "echo | set /p nul=" + info
|
||||
elif platform.system() == "Darwin": # for MacOS
|
||||
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
else: # Linux
|
||||
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
class HiddenPanel:
|
||||
@classmethod
|
||||
def false_poll(cls, context):
|
||||
return False
|
||||
|
||||
def register():
|
||||
# Only show our error panel and nothing else in the scene tab
|
||||
for item_name in dir(bpy.types):
|
||||
item = getattr(bpy.types, item_name)
|
||||
if not hasattr(item, "bl_rna") or not isinstance(item.bl_rna, bpy.types.Panel):
|
||||
continue
|
||||
if getattr(item, "bl_context", None) != "scene":
|
||||
continue
|
||||
|
||||
# Reregister scene panel with a new poll to hide it
|
||||
item.poll = HiddenPanel.false_poll
|
||||
bpy.utils.unregister_class(item)
|
||||
bpy.utils.register_class(item)
|
||||
bpy.utils.register_class(BIM_PT_fatal_error)
|
||||
bpy.utils.register_class(CopyDebugInformation)
|
||||
bpy.utils.register_class(OpenUri)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OpenUri)
|
||||
bpy.utils.unregister_class(CopyDebugInformation)
|
||||
bpy.utils.unregister_class(BIM_PT_fatal_error)
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import blenderbim
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from typing import Callable, Union
|
||||
|
||||
@@ -103,6 +103,7 @@ classes = [
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseError,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
|
||||
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -5,20 +5,20 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_An
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#6,#7));
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#9,#10,#11,#12,#13));
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/SECTION,IfcTypeProduct',(#9,#10,#11,#12,#13));
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -28,7 +28,7 @@ DATA;
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/DIMENSION,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -19,12 +19,15 @@
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import shutil
|
||||
import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import traceback
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import blenderbim
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
@@ -37,19 +40,19 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
class IfcStore:
|
||||
path: str = ""
|
||||
file: ifcopenshell.file = None
|
||||
schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
|
||||
cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
|
||||
cache_path: str = None
|
||||
file: Optional[ifcopenshell.file] = None
|
||||
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
|
||||
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
|
||||
cache_path: Optional[str] = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
pset_template_file: Optional[ifcopenshell.file] = None
|
||||
classification_path: str = ""
|
||||
classification_file: ifcopenshell.file = None
|
||||
classification_file: Optional[ifcopenshell.file] = None
|
||||
library_path: str = ""
|
||||
library_file: ifcopenshell.file = None
|
||||
library_file: Optional[ifcopenshell.file] = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -119,7 +122,13 @@ class IfcStore:
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
os.replace(IfcStore.cache_path, new_cache_path)
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
try:
|
||||
shutil.copy2(IfcStore.cache_path, new_cache_path)
|
||||
except PermissionError:
|
||||
pass # Well we tried. No cache for you!
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
@@ -329,6 +338,7 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
@@ -343,10 +353,14 @@ class IfcStore:
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
try:
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
|
||||
if is_top_level_operator:
|
||||
if tool.Ifc.get():
|
||||
|
||||
@@ -702,15 +702,17 @@ class IfcImporter:
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
point = np.array(
|
||||
point = mat @ np.array(
|
||||
(
|
||||
shape.geometry.verts[0] / self.unit_scale,
|
||||
shape.geometry.verts[1] / self.unit_scale,
|
||||
shape.geometry.verts[2] / self.unit_scale,
|
||||
shape.geometry.verts[0],
|
||||
shape.geometry.verts[1],
|
||||
shape.geometry.verts[2],
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
return mat @ point
|
||||
point = point / self.unit_scale
|
||||
if self.is_point_far_away(point, is_meters=False):
|
||||
return point
|
||||
|
||||
def does_element_likely_have_geometry_far_away(self, element):
|
||||
for representation in element.Representation.Representations:
|
||||
@@ -1500,10 +1502,9 @@ class IfcImporter:
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
types_collection = project_collection.children[self.type_collection.name]
|
||||
types_collection.hide_viewport = False
|
||||
for obj in types_collection.collection.objects: #turn off all objects inside Types collection.
|
||||
for obj in types_collection.collection.objects: # turn off all objects inside Types collection.
|
||||
obj.hide_set(True)
|
||||
|
||||
|
||||
def clean_mesh(self):
|
||||
obj = None
|
||||
last_obj = None
|
||||
@@ -1912,11 +1913,7 @@ class IfcImporter:
|
||||
and geometry.verts
|
||||
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
|
||||
):
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
offset_point = np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
@@ -1924,8 +1921,15 @@ class IfcImporter:
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
if geometry != shape:
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ offset_point
|
||||
verts = [None] * len(geometry.verts)
|
||||
for i in range(0, len(geometry.verts), 3):
|
||||
# Note: this enh2xyz call is crazy slow.
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
geometry.verts[i],
|
||||
geometry.verts[i + 1],
|
||||
|
||||
@@ -73,7 +73,7 @@ class BIM_PT_aggregate(Panel):
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
|
||||
else:
|
||||
row.label(text="No Aggregate", icon="TRIA_UP")
|
||||
row.label(text="No Whole relation defined", icon="TRIA_UP")
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -209,6 +209,10 @@ class DisableEditingClassification(bpy.types.Operator):
|
||||
class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_classification"
|
||||
bl_label = "Remove Classification"
|
||||
bl_description = (
|
||||
"The classification and all of its relationships, children references, "
|
||||
"and relationships between objects and child references will be completely removed from a project"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ class AddContext(bpy.types.Operator, Operator):
|
||||
class RemoveContext(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_context"
|
||||
bl_label = "Remove Context"
|
||||
bl_description = (
|
||||
"Remove representation context. Any representation geometry that is assigned to the context is also removed. "
|
||||
"If a context is removed, then any subcontexts are also removed"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
context: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
if props.format != "csv" and props.should_generate_svg:
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -285,6 +286,7 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
refresh_ui_data()
|
||||
self.report({"INFO"}, "Data is imported to IFC.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import random
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
@@ -34,7 +33,7 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.debug as core
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim import get_debug_info, format_debug_info
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -44,28 +43,7 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
def execute(self, context):
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
info = {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"ifc": False,
|
||||
}
|
||||
|
||||
info = get_debug_info()
|
||||
if tool.Ifc.get():
|
||||
info.update(
|
||||
{
|
||||
@@ -76,16 +54,18 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
}
|
||||
)
|
||||
|
||||
# Format it in a readable way
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
text = format_debug_info(info)
|
||||
|
||||
print("-" * 80)
|
||||
print(text)
|
||||
print("-" * 80)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
command = "echo | set /p nul=" + text.strip()
|
||||
command = "echo | set /p nul=" + text
|
||||
elif platform.system() == "Darwin": # for MacOS
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
else: # Linux
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1515,7 +1515,14 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
try:
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
except core.CameraNotAvailableError:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The drawing view is not available. Ensure you have not excluded it in the active view layer.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
@@ -136,19 +136,23 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
"for Profile - 2D bounding box by local XZ axes.\n"
|
||||
"For other contexts - bounding box is 3d.",
|
||||
),
|
||||
("PROJECT", "Full Representation", ""),
|
||||
("OBJECT", "From Object", "Copies geometry from another object"),
|
||||
("PROJECT", "Full Representation", "Reuses the current representation"),
|
||||
],
|
||||
name="Representation Conversion Method",
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_context = int(props.contexts or "0") or None
|
||||
props = context.scene.BIMGeometryProperties
|
||||
oprops = obj.BIMGeometryProperties
|
||||
ifc_context = int(oprops.contexts or "0") or None
|
||||
if not ifc_context:
|
||||
return
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
|
||||
original_data = obj.data
|
||||
|
||||
if self.representation_conversion_method == "OUTLINE":
|
||||
if ifc_context.ContextType == "Plan":
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||
@@ -165,17 +169,31 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
else:
|
||||
data = tool.Geometry.generate_3d_box_mesh(obj)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
elif (
|
||||
self.representation_conversion_method == "OBJECT"
|
||||
and props.representation_from_object
|
||||
and props.representation_from_object.data
|
||||
):
|
||||
data = tool.Geometry.duplicate_object_data(props.representation_from_object)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
try:
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
except core.IncompatibleRepresentationError:
|
||||
if obj.data != original_data:
|
||||
tool.Geometry.change_object_data(obj, original_data, is_global=True)
|
||||
bpy.data.meshes.remove(data)
|
||||
self.report({"ERROR"}, "No compatible representation for the context could be created.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -183,6 +201,9 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "representation_conversion_method", text="")
|
||||
if self.representation_conversion_method == "OBJECT":
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
|
||||
|
||||
|
||||
class SelectConnection(bpy.types.Operator, Operator):
|
||||
@@ -215,6 +236,13 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.active_object.mode == "OBJECT":
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode - Press TAB in the viewport")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
target_representation = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
target = target_representation.ContextOfItems
|
||||
@@ -223,6 +251,8 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if not obj.mode == "OBJECT":
|
||||
continue
|
||||
if obj == context.active_object:
|
||||
representation = target_representation
|
||||
else:
|
||||
@@ -909,7 +939,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
@@ -917,7 +947,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], product=new[0])
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[new[0]])
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
@@ -987,7 +1017,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
else:
|
||||
index = add_linked_aggregate_pset(part, index)
|
||||
# index += 1
|
||||
|
||||
|
||||
obj = tool.Ifc.get_object(part)
|
||||
obj.select_set(True)
|
||||
|
||||
@@ -1021,9 +1051,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
return
|
||||
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
|
||||
ifcopenshell.api.run(
|
||||
"group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group
|
||||
)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
for new in old_to_new.values():
|
||||
@@ -1047,10 +1075,10 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if re.findall(pattern2, new_obj.name):
|
||||
split_name = new_obj.name.split(".")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
|
||||
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i['Index'] for i in psets if i]
|
||||
index = [i["Index"] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
return index
|
||||
@@ -1064,14 +1092,14 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
new_pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=new[0], name=self.pset_name
|
||||
)
|
||||
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=new_pset,
|
||||
properties={"Index": pset["Index"]},
|
||||
)
|
||||
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
@@ -1080,7 +1108,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.assign_group", group=linked_aggregate_group[0], products=new)
|
||||
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
@@ -1101,7 +1128,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
select_objects_and_add_data(selected_element)
|
||||
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
|
||||
tool.Root.recreate_aggregate(old_to_new)
|
||||
|
||||
copy_linked_aggregate_data(old_to_new)
|
||||
@@ -1250,9 +1277,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
selected_matrix = selected_obj.matrix_world
|
||||
object_duplicate = tool.Ifc.get_object(element)
|
||||
duplicate_matrix = object_duplicate.matrix_world.decompose()
|
||||
|
||||
|
||||
return selected_matrix, duplicate_matrix
|
||||
|
||||
|
||||
def set_new_matrix(selected_matrix, duplicate_matrix, old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
@@ -1260,7 +1287,6 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
|
||||
new_obj_matrix = new_base_matrix @ matrix_diff
|
||||
new_obj.matrix_world = new_obj_matrix
|
||||
|
||||
|
||||
active_element = tool.Ifc.get_entity(context.active_object)
|
||||
if not active_element:
|
||||
@@ -1446,14 +1472,13 @@ class OverridePasteBuffer(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.pastebuffer()
|
||||
if IfcStore.get_file():
|
||||
for obj in context.selected_objects:
|
||||
# Pasted objects may come from another Blender session, or even
|
||||
# from the same session where the original object has since
|
||||
# been deleted. As the source element may not exist, paste will
|
||||
# always unlink the element. If you want to duplicate an
|
||||
# element, use the duplicate commands.
|
||||
tool.Root.unlink_object(obj)
|
||||
for obj in context.selected_objects:
|
||||
# Pasted objects may come from another Blender session, or even
|
||||
# from the same session where the original object has since
|
||||
# been deleted. As the source element may not exist, paste will
|
||||
# always unlink the element. If you want to duplicate an
|
||||
# element, use the duplicate commands.
|
||||
tool.Root.unlink_object(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -138,3 +138,4 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
name="IFC Interaction Mode",
|
||||
update=update_mode,
|
||||
)
|
||||
representation_from_object: PointerProperty(type=bpy.types.Object)
|
||||
|
||||
@@ -83,8 +83,17 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props = obj.BIMArrayProperties
|
||||
|
||||
relating_obj = props.relating_array_object
|
||||
|
||||
if relating_obj:
|
||||
element = tool.Ifc.get_entity(relating_obj)
|
||||
parent_globalid = ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Parent")
|
||||
parent_element = tool.Ifc.get().by_guid(parent_globalid)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data"))[self.item]
|
||||
else:
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props.count = data["count"]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props.x = data["x"] * si_conversion
|
||||
@@ -93,7 +102,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
|
||||
props.is_editing = self.item
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -137,6 +148,10 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
#clears the relating_array_object so it doesn't show again next time
|
||||
props.relating_array_object = None
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -87,6 +87,21 @@ def update_type_page(self, context):
|
||||
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
|
||||
|
||||
|
||||
def update_relating_array_from_object(self, context):
|
||||
bpy.ops.bim.enable_editing_array(item=self.is_editing)
|
||||
return
|
||||
|
||||
|
||||
def is_object_array_applicable(self, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return False
|
||||
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
@@ -203,6 +218,14 @@ class BIMArrayProperties(PropertyGroup):
|
||||
description="Regenerate all children based on the parent object",
|
||||
default=False,
|
||||
)
|
||||
relating_array_object: bpy.props.PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="Copy Array Properties",
|
||||
update=update_relating_array_from_object,
|
||||
poll=is_object_array_applicable,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
|
||||
@@ -137,6 +137,7 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
|
||||
op.element = relating_type["id"]
|
||||
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
|
||||
op.relating_type = relating_type["id"]
|
||||
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
|
||||
op.element = relating_type["id"]
|
||||
op = row.operator("bim.remove_type", icon="X", text="")
|
||||
@@ -222,6 +223,8 @@ class BIM_PT_array(bpy.types.Panel):
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "z")
|
||||
row.operator("bim.input_cursor_z_array", icon="CURSOR", text="")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "relating_array_object", icon="COPYDOWN")
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
name = f"{array['count']} Items ({array.get('method', 'OFFSET').capitalize()})"
|
||||
|
||||
@@ -378,6 +378,7 @@ class BimToolUI:
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
|
||||
if AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegment",
|
||||
@@ -385,8 +386,8 @@ class BimToolUI:
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_Y", "")
|
||||
if context.region.type != "TOOL_HEADER":
|
||||
cls.layout.operator("bim.mep_add_bend")
|
||||
cls.layout.operator("bim.mep_add_transition")
|
||||
@@ -394,7 +395,6 @@ class BimToolUI:
|
||||
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
@@ -719,10 +719,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcBeam", "IfcColumn"):
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.flip_object(flip_local_axes="XZ")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
|
||||
def hotkey_S_G(self):
|
||||
obj = bpy.context.active_object
|
||||
@@ -808,9 +807,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.join_wall(join_type="V")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="V")
|
||||
|
||||
|
||||
def hotkey_S_B(self):
|
||||
bpy.ops.bim.add_boundary()
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
import traceback
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
@@ -674,19 +675,23 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
return self.finish_loading_project(context)
|
||||
|
||||
def finish_loading_project(self, context):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
try:
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
if tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
context.scene.BIMProperties.ifc_file = self.get_filepath()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
@@ -268,7 +268,6 @@ class RemovePropTemplate(bpy.types.Operator, Operator):
|
||||
prop_template: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.remove_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
@@ -287,21 +286,21 @@ class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||
enumerator = self.generate_prop_enum(props)
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
prop = props.active_prop_template
|
||||
enumerators = [getattr(ev, data_type) for ev in prop.enum_values]
|
||||
else:
|
||||
enumerator = None
|
||||
enumerators = None
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.edit_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
**{
|
||||
"prop_template": IfcStore.pset_template_file.by_id(props.active_prop_template_id),
|
||||
"attributes": {
|
||||
"Name": props.active_prop_template.name,
|
||||
"Description": props.active_prop_template.description,
|
||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||
"TemplateType": props.active_prop_template.template_type,
|
||||
"Enumerators": enumerator,
|
||||
},
|
||||
prop_template=IfcStore.pset_template_file.by_id(props.active_prop_template_id),
|
||||
attributes={
|
||||
"Name": props.active_prop_template.name,
|
||||
"Description": props.active_prop_template.description,
|
||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||
"TemplateType": props.active_prop_template.template_type,
|
||||
"Enumerators": enumerators,
|
||||
}
|
||||
)
|
||||
bpy.ops.bim.disable_editing_prop_template()
|
||||
@@ -309,18 +308,3 @@ class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
if tool.Ifc.get():
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
# TODO -This will need to go into the
|
||||
# api code at some point - vulevukusej
|
||||
def generate_prop_enum(self, props):
|
||||
self.file = IfcStore.pset_template_file
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
prop = props.active_prop_template
|
||||
prop_enum = self.file.create_entity(
|
||||
"IFCPROPERTYENUMERATION",
|
||||
Name=prop.name,
|
||||
EnumerationValues=tuple(
|
||||
self.file.create_entity(prop.primary_measure_type, getattr(ev, data_type)) for ev in prop.enum_values
|
||||
),
|
||||
)
|
||||
return prop_enum
|
||||
|
||||
@@ -185,6 +185,8 @@ class IfcClassData:
|
||||
if element:
|
||||
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
|
||||
return False
|
||||
if element.is_a() in ("IfcWindowStyle", "IfcDoorStyle"): #see https://github.com/IfcOpenShell/IfcOpenShell/issues/4622#issuecomment-2095676368
|
||||
return True
|
||||
for product in cls.ifc_products():
|
||||
if element.is_a(product[0]):
|
||||
return True
|
||||
|
||||
@@ -20,6 +20,7 @@ import re
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
from ifcopenshell.util.selector import Selector
|
||||
|
||||
@@ -146,41 +146,51 @@ class SelectType(bpy.types.Operator):
|
||||
relating_type: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
selected_objs = context.selected_objects
|
||||
active_obj = context.active_object
|
||||
selected_objs.append(active_obj) #update selected_objs so the active_obj is at the end of the list
|
||||
|
||||
if self.relating_type: #if operator button sends a relating_type, the iterator only selects this one type
|
||||
element = tool.Ifc.get().by_id(self.relating_type)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
selected_objs = [obj]
|
||||
else: #else, the iterator selects all the types of all the selected objects
|
||||
selected_objs = context.selected_objects
|
||||
active_obj = context.active_object
|
||||
selected_objs.append(active_obj) #update selected_objs so the active_obj is at the end of the list
|
||||
|
||||
last_relating_type_obj = None
|
||||
types_collection_in_view_layer = self.find_collection_in_ifcproject(context, collection_name = "Types")
|
||||
types_collection_in_view_layer.hide_viewport = False
|
||||
types_collection = bpy.data.collections.get("Types")
|
||||
for obj in types_collection.objects:
|
||||
obj.hide_set(True)
|
||||
for type_obj in types_collection.objects:
|
||||
type_obj.hide_set(True)
|
||||
for obj in selected_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
relating_type = ifcopenshell.util.element.get_type(element)
|
||||
relating_type_obj = tool.Ifc.get_object(relating_type)
|
||||
obj.select_set(False)
|
||||
if relating_type_obj:
|
||||
if relating_type_obj.hide_get():
|
||||
relating_type_obj.hide_set(False)
|
||||
relating_type_obj.select_set(True)
|
||||
last_relating_type_obj = relating_type_obj
|
||||
if relating_type:
|
||||
relating_type_obj = tool.Ifc.get_object(relating_type)
|
||||
if relating_type_obj:
|
||||
if relating_type_obj.hide_get():
|
||||
relating_type_obj.hide_set(False)
|
||||
relating_type_obj.select_set(True)
|
||||
last_relating_type_obj = relating_type_obj
|
||||
if not element.is_a("IfcTypeObject"):
|
||||
obj.select_set(False)
|
||||
|
||||
context.view_layer.objects.active = last_relating_type_obj #makes the active_obj's type the active object
|
||||
|
||||
context.view_layer.objects.active = last_relating_type_obj #make the active_obj's type the active object
|
||||
|
||||
# if relating_type_obj:
|
||||
# try:
|
||||
# tool.Blender.select_and_activate_single_object(context, relating_type_obj)
|
||||
# except:
|
||||
# self.report({"INFO"}, "Type object is hidden.")
|
||||
# # IfcTypeProducts are only used for annotations and not part of the model interface.
|
||||
# if element.is_a() != "IfcTypeProduct":
|
||||
# try:
|
||||
# context.scene.BIMModelProperties.ifc_class = element.is_a()
|
||||
# context.scene.BIMModelProperties.relating_type_id = str(relating_type)
|
||||
# except:
|
||||
# # Potentially our BIM Tool is filtered to a specific element.
|
||||
# pass
|
||||
return {"FINISHED"}
|
||||
|
||||
def find_collection_in_ifcproject(self, context, collection_name):
|
||||
|
||||
ifc_project_collection = None
|
||||
for child in context.view_layer.layer_collection.children:
|
||||
if "IfcProject" in child.name:
|
||||
ifc_project_collection = child
|
||||
break
|
||||
|
||||
if ifc_project_collection:
|
||||
collection_in_view_layer = ifc_project_collection.children.get(collection_name)
|
||||
return collection_in_view_layer
|
||||
|
||||
|
||||
class SelectSimilarType(bpy.types.Operator):
|
||||
bl_idname = "bim.select_similar_type"
|
||||
|
||||
@@ -88,7 +88,7 @@ class BIM_PT_type(Panel):
|
||||
if TypeData.data["relating_type"]:
|
||||
row.label(text=TypeData.data["relating_type"]["name"])
|
||||
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
|
||||
op.relating_type = TypeData.data["relating_type"]["id"]
|
||||
op.relating_type = 0 #will only select the relating types of only the selected objects
|
||||
row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
|
||||
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.unassign_type", icon="X", text="")
|
||||
|
||||
@@ -90,6 +90,15 @@ class OpenUri(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CloseError(bpy.types.Operator):
|
||||
bl_idname = "bim.close_error"
|
||||
bl_label = "Close Error"
|
||||
|
||||
def execute(self, context):
|
||||
blenderbim.last_error = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectURIAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.select_uri_attribute"
|
||||
bl_label = "Select URI Attribute"
|
||||
|
||||
@@ -29,8 +29,9 @@ from ifcopenshell.util.doc import (
|
||||
get_attribute_doc,
|
||||
)
|
||||
from . import ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim import get_debug_info
|
||||
import blenderbim.bim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import IfcHeaderExtractor
|
||||
from blenderbim.bim.prop import Attribute
|
||||
|
||||
@@ -402,6 +403,17 @@ class BIM_PT_tabs(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(aprops, "tab", text="")
|
||||
|
||||
if blenderbim.last_error:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
|
||||
row.operator("bim.close_error", text="", icon="CANCEL")
|
||||
box.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = box.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
|
||||
|
||||
except:
|
||||
pass # Prior to load_post, we may not have any area properties setup
|
||||
|
||||
|
||||
@@ -441,9 +441,19 @@ def select_assigned_product(drawing, context):
|
||||
drawing.select_assigned_product(context)
|
||||
|
||||
|
||||
def activate_drawing_view(ifc, drawing_tool, drawing):
|
||||
def activate_drawing_view(ifc, blender, drawing_tool, drawing):
|
||||
camera = ifc.get_object(drawing)
|
||||
if not camera:
|
||||
camera = drawing_tool.import_drawing(drawing)
|
||||
drawing_tool.import_annotations_in_group(drawing_tool.get_drawing_group(drawing))
|
||||
blender.activate_camera(camera)
|
||||
drawing_tool.isolate_camera_collection(camera)
|
||||
try:
|
||||
blender.set_active_object(camera)
|
||||
except:
|
||||
raise CameraNotAvailableError()
|
||||
drawing_tool.activate_drawing(camera)
|
||||
|
||||
|
||||
class CameraNotAvailableError(Exception):
|
||||
pass
|
||||
|
||||
@@ -47,7 +47,7 @@ def add_representation(
|
||||
data = geometry.get_object_data(obj)
|
||||
|
||||
if not data and ifc_representation_class != "IfcTextLiteral":
|
||||
return
|
||||
raise IncompatibleRepresentationError()
|
||||
|
||||
representation = ifc.run(
|
||||
"geometry.add_representation",
|
||||
@@ -63,6 +63,9 @@ def add_representation(
|
||||
profile_set_usage=profile_set_usage,
|
||||
)
|
||||
|
||||
if not representation:
|
||||
raise IncompatibleRepresentationError()
|
||||
|
||||
if geometry.is_body_representation(representation):
|
||||
[geometry.run_style_add_style(obj=mat) for mat in geometry.get_object_materials_without_styles(obj)]
|
||||
ifc.run(
|
||||
@@ -221,3 +224,7 @@ def edit_similar_opening_placement(geometry, opening=None, similar_openings=None
|
||||
old_placement = similar_opening.ObjectPlacement
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
geometry.delete_opening_object_placement(old_placement)
|
||||
|
||||
|
||||
class IncompatibleRepresentationError(Exception):
|
||||
pass
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
from typing import Optional
|
||||
|
||||
# fmt: off
|
||||
# pylint: skip-file
|
||||
@@ -72,20 +73,21 @@ class Aggregate:
|
||||
|
||||
@interface
|
||||
class Blender:
|
||||
def set_active_object(cls, obj): pass
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def create_ifc_object(cls, ifc_class: str, name: str = None, data=None): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def get_viewport_context(cls): pass
|
||||
def update_viewport(cls): pass
|
||||
def get_default_selection_keypmap(cls): pass
|
||||
def get_object_bounding_box(cls, obj): pass
|
||||
def activate_camera(cls, obj): pass
|
||||
def apply_bmesh(cls, mesh, bm, obj=None): pass
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False): pass
|
||||
def bmesh_join(cls, bm_a, bm_b, callback=None): pass
|
||||
def create_ifc_object(cls, ifc_class: str, name: Optional[str] = None, data=None): pass
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False): pass
|
||||
def get_default_selection_keypmap(cls): pass
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def get_object_bounding_box(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_viewport_context(cls): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
def set_active_object(cls, obj): pass
|
||||
def update_viewport(cls): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -49,6 +49,23 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE = ("GPENCIL",)
|
||||
TYPE_MANAGER_ICON = "LIGHTPROBE_VOLUME" if bpy.app.version >= (4, 1, 0) else "LIGHTPROBE_GRID"
|
||||
|
||||
@classmethod
|
||||
def activate_camera(cls, obj: bpy.types.Object) -> None:
|
||||
area = tool.Blender.get_view3d_area()
|
||||
is_local_view = area.spaces[0].local_view is not None
|
||||
if is_local_view:
|
||||
# Turn off local view before activating drawing, and then turn it on again.
|
||||
for a in bpy.context.screen.areas:
|
||||
if a.type == "VIEW_3D":
|
||||
override = bpy.context.copy()
|
||||
override["area"] = a
|
||||
bpy.ops.view3d.localview(override)
|
||||
bpy.context.scene.camera = obj
|
||||
bpy.ops.view3d.localview(override)
|
||||
else:
|
||||
bpy.context.scene.camera = obj
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
|
||||
@classmethod
|
||||
def get_area_props(cls, context: bpy.types.Context) -> Any:
|
||||
try:
|
||||
@@ -845,9 +862,7 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
bpy.utils.register_tool(ws_model.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(ws_model.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(ws_drawing.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(
|
||||
ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False
|
||||
)
|
||||
bpy.utils.register_tool(ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(
|
||||
ws_structural.StructuralTool, after={"bim.spatial_tool"}, separator=False, group=False
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.brick
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
@@ -604,4 +605,4 @@ class BrickStore:
|
||||
def set_last_saved(cls):
|
||||
save = os.path.getmtime(BrickStore.path)
|
||||
save = datetime.datetime.fromtimestamp(save)
|
||||
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
|
||||
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
|
||||
|
||||
@@ -1743,21 +1743,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.ops.bim.activate_model()
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
area = tool.Blender.get_view3d_area()
|
||||
is_local_view = area.spaces[0].local_view is not None
|
||||
if is_local_view:
|
||||
# turn off local view before activating drawing, and then turn it on again.
|
||||
for a in bpy.context.screen.areas:
|
||||
if a.type == "VIEW_3D":
|
||||
override = bpy.context.copy()
|
||||
override["area"] = a
|
||||
bpy.ops.view3d.localview(override)
|
||||
bpy.context.scene.camera = camera
|
||||
bpy.ops.view3d.localview(override)
|
||||
else:
|
||||
bpy.context.scene.camera = camera
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
def isolate_camera_collection(cls, camera: bpy.types.Object) -> None:
|
||||
views_collection = bpy.data.collections.get("Views")
|
||||
for collection in views_collection.children:
|
||||
# We assume the project collection is at the top level
|
||||
@@ -1775,8 +1761,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
camera.BIMObjectProperties.collection.name
|
||||
].hide_viewport = False
|
||||
camera.BIMObjectProperties.collection.hide_render = False
|
||||
tool.Spatial.set_active_object(camera)
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.system
|
||||
import blenderbim.core.tool
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import lark
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.selector
|
||||
from ifcopenshell.util.selector import Selector
|
||||
from blenderbim.bim.prop import BIMFacet
|
||||
@@ -160,10 +161,16 @@ class ImportFilterQueryTransformer(lark.Transformer):
|
||||
return args[0]
|
||||
|
||||
def instance(self, args):
|
||||
return {"type": "instance", "value": " ".join([a.children[0].value for a in args])}
|
||||
if args[0].data == "not":
|
||||
return {"type": "instance", "value": "!" + args[1].children[0].value}
|
||||
else:
|
||||
return {"type": "instance", "value": args[0].children[0].value}
|
||||
|
||||
def entity(self, args):
|
||||
return {"type": "entity", "value": " ".join([a.children[0].value for a in args])}
|
||||
if args[0].data == "not":
|
||||
return {"type": "entity", "value": "!" + args[1].children[0].value}
|
||||
else:
|
||||
return {"type": "entity", "value": args[0].children[0].value}
|
||||
|
||||
def attribute(self, args):
|
||||
name, comparison, value = args
|
||||
|
||||
@@ -111,16 +111,17 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
for rel in parent.IsDecomposedBy or []:
|
||||
related_objects = []
|
||||
for element in rel.RelatedObjects:
|
||||
# skip objects without placements
|
||||
if not element.is_a("IfcProduct"):
|
||||
continue
|
||||
related_objects.append((element, ifcopenshell.util.placement.get_storey_elevation(element)))
|
||||
related_objects = sorted(related_objects, key=lambda e: e[1])
|
||||
for element in related_objects:
|
||||
element = element[0]
|
||||
for element, _ in related_objects:
|
||||
new = props.containers.add()
|
||||
new.name = element.Name or "Unnamed"
|
||||
new.long_name = element.LongName or ""
|
||||
new.has_decomposition = bool(element.IsDecomposedBy)
|
||||
new.ifc_definition_id = element.id()
|
||||
new.elevation = element[1]
|
||||
|
||||
@classmethod
|
||||
def run_root_copy_class(cls, obj=None):
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ a {
|
||||
}
|
||||
.sidebar-brand-text {
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.blockbutton {
|
||||
max-width: 500px;
|
||||
|
||||
@@ -95,7 +95,10 @@ html_theme_options = {
|
||||
"color-background-border": "#cfd0cb",
|
||||
"color-foreground-primary": "#2e3436",
|
||||
"color-sidebar-item-background--hover": "#f7f7f6",
|
||||
"color-link": "#39b54a",
|
||||
"color-link--visited": "#39b54a",
|
||||
"color-link--hover": "#d98014",
|
||||
"color-link--visited--hover": "#d98014",
|
||||
"font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
|
||||
},
|
||||
"dark_css_variables": {
|
||||
@@ -106,7 +109,10 @@ html_theme_options = {
|
||||
"color-background-border": "#2e3436",
|
||||
"color-foreground-primary": "#eeeeec",
|
||||
"color-sidebar-item-background--hover": "#2e3436",
|
||||
"color-link": "#39b54a",
|
||||
"color-link--visited": "#39b54a",
|
||||
"color-link--hover": "#d98014",
|
||||
"color-link--visited--hover": "#d98014",
|
||||
"font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
|
||||
},
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ Unstable installation
|
||||
|
||||
**Unstable installation** is almost the same as **Stable installation**, except
|
||||
that they are typically updated every day. Simply download a daily build from
|
||||
the `Github releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the same
|
||||
instructions as the **Stable installation**.
|
||||
the `GitHub releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the
|
||||
usual :doc:`installation instructions</users/installation>`.
|
||||
|
||||
You will need to choose which build to download.
|
||||
|
||||
- If you are on Blender >=4.1, choose py311
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py10
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py310
|
||||
- If you are on Blender >=2.93 and <3.1, choose py39
|
||||
- Choose ``linux``, ``macos`` (Apple Intel), ``macosm1`` (Apple Silicon), or
|
||||
``win`` depending on your operating system
|
||||
@@ -92,13 +92,14 @@ For Linux or Mac:
|
||||
$ ln -s $PWD/src/blenderbim/blenderbim/tool $BLENDER_ADDON_PATH/tool
|
||||
$ ln -s $PWD/src/blenderbim/blenderbim/bim $BLENDER_ADDON_PATH/bim
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/api
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/util
|
||||
# Copy over compiled IfcOpenShell files
|
||||
$ cp $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/*_wrapper* $PWD/src/ifcopenshell-python/ifcopenshell/
|
||||
|
||||
# Remove the IfcOpenShell dependency
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s $PWD/src/ifcopenshell-python/ifcopenshell/api $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/api
|
||||
$ ln -s $PWD/src/ifcopenshell-python/ifcopenshell/util $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/util
|
||||
$ ln -s $PWD/src/ifcopenshell-python/ifcopenshell $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
|
||||
|
||||
# Remove and link other IfcOpenShell utilities
|
||||
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
|
||||
@@ -153,21 +154,19 @@ Before running it follow the instructions descibed after `rem` tags.
|
||||
rd /S /Q "%blenderbim%\tool\"
|
||||
rd /S /Q "%blenderbim%\bim\"
|
||||
|
||||
|
||||
echo Replacing them with links to the Git repository...
|
||||
mklink /D "%blenderbim%\core" "%cd%\src\blenderbim\blenderbim\core"
|
||||
mklink /D "%blenderbim%\tool" "%cd%\src\blenderbim\blenderbim\tool"
|
||||
mklink /D "%blenderbim%\bim" "%cd%\src\blenderbim\blenderbim\bim"
|
||||
|
||||
echo Copy over compiled IfcOpenShell files...
|
||||
copy "%blenderbim%\libs\site\packages\ifcopenshell\*_wrapper*" "%cd%\src\ifcopenshell-python\ifcopenshell\"
|
||||
|
||||
echo Remove the IfcOpenShell dependency Python code...
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifcopenshell\api"
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifcopenshell\util"
|
||||
echo Remove the IfcOpenShell dependency...
|
||||
rd /S /Q "%blenderbim%\libs\site\packages\ifcopenshell"
|
||||
|
||||
|
||||
echo Replacing them with links to the Git repository...
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifcopenshell\api" "%cd%\src\ifcopenshell-python\ifcopenshell\api"
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifcopenshell\util" "%cd%\src\ifcopenshell-python\ifcopenshell\util"
|
||||
echo Replace them with links to the Git repository...
|
||||
mklink /D "%blenderbim%\libs\site\packages\ifcopenshell" "%cd%\src\ifcopenshell-python\ifcopenshell"
|
||||
|
||||
echo Remove and link other IfcOpenShell utilities...
|
||||
del "%blenderbim%\libs\site\packages\ifccsv.py"
|
||||
|
||||
@@ -16,9 +16,12 @@ You can press the edit button on the top right on any documentation page to
|
||||
quickly edit their content.
|
||||
|
||||
You can link to `external websites
|
||||
<https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html>`_.
|
||||
You can also link to sections on the same page, like `Writing technical
|
||||
documentation`_. You can link to other pages, like :doc:`Hello
|
||||
<https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html>`_
|
||||
(note the space between the url and the link text). You can also link to
|
||||
sections on the same page, like :ref:`devs/writing_docs:Writing technical
|
||||
documentation` or with :ref:`custom text<devs/writing_docs:writing technical
|
||||
documentation>`. Traditional references like `Writing technical documentation`_
|
||||
work too but are discouraged. You can link to other pages, like :doc:`Hello
|
||||
World<hello_world>` or sections within other pages, like
|
||||
:ref:`devs/installation:unstable installation`. We have ``autosectionlabel``
|
||||
enabled so it is not necessary to manually create labels.
|
||||
|
||||
@@ -32,6 +32,7 @@ Learn how to model a small building and create simple architectural plans, secti
|
||||
users/git_support
|
||||
users/other_addons
|
||||
users/general_usage
|
||||
users/troubleshooting
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -58,6 +58,32 @@ You can enable add-ons permanently by using ``Save User Settings`` from the Addo
|
||||
|
||||
.. _where is the add-on installed:
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First follow the `Uninstalling`_ section below, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to **first disable the BlenderBIM Add-on in your Blender preferences**
|
||||
by pressing the checkbox next to the add-on, then **restart Blender**. It is
|
||||
critical to follow this sequence of disabling first, and then restarting.
|
||||
|
||||
After restarting, you can uninstall the BlenderBIM Add-on by pressing the
|
||||
``Remove`` button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim``
|
||||
directory in :ref:`your Blender add-ons directory<where is the add-on
|
||||
installed>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
Where is the add-on installed?
|
||||
------------------------------
|
||||
|
||||
@@ -66,118 +92,44 @@ Upon installation, the BlenderBIM Add-on is stored in the
|
||||
folder. However, the location of your Blender configuration folder depends on
|
||||
how you have installed Blender.
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
||||
will find the Blender configuration folder in the following directory, where
|
||||
``X.XX`` is the Blender version:
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, the
|
||||
BlenderBIM Add-on will be installed in the following directory, where ``X.XX``
|
||||
is the Blender version:
|
||||
|
||||
::
|
||||
|
||||
/path/to/blender/X.XX/
|
||||
/path/to/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
Otherwise, if you installed Blender using an installation package, the Blender
|
||||
configuration folder depends on which operating system you use.
|
||||
|
||||
On Linux, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
~/.config/blender/X.XX/
|
||||
~/.config/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Linux, if you are deploying the add-on system-wide (this may also depend on
|
||||
your Linux distribution):
|
||||
|
||||
::
|
||||
|
||||
/usr/share/blender/X.XX/
|
||||
/usr/share/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are deploying the add-on system-wide:
|
||||
|
||||
::
|
||||
|
||||
/Library/Application Support/Blender/X.XX/
|
||||
/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First uninstall the current BlenderBIM add-on, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to first disable the BlenderBIM Add-on in your Blender preferences by
|
||||
pressing the checkbox next to the add-on, then restart Blender. After
|
||||
restarting, you can uninstall the BlenderBIM Add-on by pressing the ``Remove``
|
||||
button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim/``
|
||||
directory in your Blender add-ons directory.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Sometimes it is helpful to try installing and using the BlenderBIM Add-on on
|
||||
a "clean environment". A clean environment is defined as a fresh Blender
|
||||
installation with no other add-ons enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, find your Blender configuration
|
||||
folder based on the `where is the add-on installed`_ section. Rename the
|
||||
folder from ``X.XX`` to something else like ``X.XX_backup``, then restart
|
||||
Blender and try follow the installation instructions again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
|
||||
|
||||
If you are using a Mac, be sure to use the Mac Silicon version if you have a
|
||||
newer Mac. The only exception is if you have installed Blender using Steam
|
||||
on a Mac, in which case you need to use the Mac Intel download.
|
||||
|
||||
For all other scenarios, check the BlenderBIM Add-on zip file which you
|
||||
downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
|
||||
the name. See the instructions in the :ref:`devs/installation:unstable
|
||||
installation` section to check that you have installed the correct version.
|
||||
|
||||
3. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
5. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\scripts\addons\blenderbim\
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
The BlenderBIM Add-on is alpha software. There are many bugs! When something
|
||||
goes wrong, you may see some computer code flash up on your screen. You may
|
||||
also see an error message:
|
||||
|
||||
.. image:: images/error-message.png
|
||||
|
||||
**Don't panic!** Click on the button that says **Copy Error Message To
|
||||
Clipboard**. You will need to paste this text in a bug report.
|
||||
|
||||
If you do not have a GitHub account, you will need to sign up to report a bug.
|
||||
In addition to pasting the error message text, please also describe what you
|
||||
were doing, and attach your IFC file or screenshots if relevant.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Report a bug <https://github.com/IfcOpenShell/IfcOpenShell/issues/new>`__
|
||||
|
||||
If your issue is particularly complex, you can also chat live with developers
|
||||
or other powerusers.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Chat live with a developer <https://osarch.org/chat>`_
|
||||
|
||||
Installation issues
|
||||
-------------------
|
||||
|
||||
If you are unable to install the BlenderBIM Add-on, make sure you are using
|
||||
**Blender 4.1** installed from https://blender.org/ and are installing the
|
||||
latest version from https://blenderbim.org.
|
||||
|
||||
Other common solutions are listed below. If none of these fix the problem, you
|
||||
can `report a bug <https://github.com/ifcopenshell/ifcopenshell/issues>`_ or
|
||||
`live chat with a developer <https://osarch.org/chat/>`_.
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Try installing and using the BlenderBIM Add-on on a "clean environment". A
|
||||
clean environment is a fresh Blender installation with no other add-ons
|
||||
enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, first :ref:`find your Blender
|
||||
configuration folder<users/installation:where is the add-on installed?>`.
|
||||
Rename the folder from ``X.XX`` to something else like ``X.XX_backup``, then
|
||||
restart Blender and try follow the :doc:`installation
|
||||
instructions<installation>` again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
|
||||
|
||||
If you are using a Mac, be sure to use the Mac Silicon version if you have a
|
||||
newer Mac. The only exception is if you have installed Blender using Steam
|
||||
on a Mac, in which case you need to use the Mac Intel download.
|
||||
|
||||
For all other scenarios, check the BlenderBIM Add-on zip file which you
|
||||
downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
|
||||
the name. See the instructions in the :ref:`devs/installation:unstable
|
||||
installation` section to check that you have installed the correct version.
|
||||
|
||||
3. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.7.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
Common issues
|
||||
-------------
|
||||
|
||||
1. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import os
|
||||
|
||||
class IFC4Extractor:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ezdxf
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import pymeshlab
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.guid
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -23,6 +23,7 @@ import pywavefront
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.guid
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -22,6 +22,7 @@ import brickschema
|
||||
import brickschema.persistent
|
||||
from brickschema.namespaces import REF, A
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from rdflib.namespace import RDF
|
||||
|
||||
@@ -21,6 +21,7 @@ from pathlib import Path
|
||||
import bpy
|
||||
import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import os
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from datetime import datetime
|
||||
|
||||
from .geometry import GeometryIO
|
||||
|
||||
+20
-12
@@ -61,20 +61,20 @@ class IfcCsv:
|
||||
|
||||
def export(
|
||||
self,
|
||||
ifc_file,
|
||||
elements,
|
||||
ifc_file: ifcopenshell.file,
|
||||
elements: ifcopenshell.entity_instance,
|
||||
attributes,
|
||||
headers=None,
|
||||
output=None,
|
||||
format=None,
|
||||
should_preserve_existing=False,
|
||||
include_global_id=True,
|
||||
delimiter=",",
|
||||
null="-",
|
||||
empty="",
|
||||
bool_true="YES",
|
||||
bool_false="NO",
|
||||
concat=", ",
|
||||
should_preserve_existing: bool = False,
|
||||
include_global_id: bool = True,
|
||||
delimiter: str = ",",
|
||||
null: str = "-",
|
||||
empty: str = "",
|
||||
bool_true: str = "YES",
|
||||
bool_false: str = "NO",
|
||||
concat: str = ", ",
|
||||
sort=None,
|
||||
groups=None,
|
||||
summaries=None,
|
||||
@@ -382,8 +382,16 @@ class IfcCsv:
|
||||
return ["{}.{}".format(pset_qto_name, n) for n in results]
|
||||
|
||||
def Import(
|
||||
self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO"
|
||||
):
|
||||
self,
|
||||
ifc_file: ifcopenshell.file,
|
||||
table: str,
|
||||
attributes: Optional[list[Union[str, None]]] = None,
|
||||
delimiter: str = ",",
|
||||
null: str = "-",
|
||||
empty: str = "",
|
||||
bool_true: str = "YES",
|
||||
bool_false: str = "NO",
|
||||
) -> None:
|
||||
ext = table.split(".")[-1].lower()
|
||||
|
||||
if ext == "csv":
|
||||
|
||||
@@ -72,8 +72,8 @@ def get_facility_data(ifc_file, element):
|
||||
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
||||
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
||||
"ModelBuildingID": element.GlobalId,
|
||||
"LinearUnits": "millimeters",
|
||||
"AreaUnits": "square meters",
|
||||
"LengthUnit": "millimeters",
|
||||
"AreaUnit": "square meters",
|
||||
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ def get_space_data(ifc_file, element):
|
||||
"Description": element.LongName,
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||
"StoreyName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
@@ -261,8 +261,8 @@ config = {
|
||||
"ModelProjectID",
|
||||
"ModelSiteID",
|
||||
"ModelBuildingID",
|
||||
"LinearUnits",
|
||||
"AreaUnits",
|
||||
"LengthUnit",
|
||||
"AreaUnit",
|
||||
"Phase",
|
||||
],
|
||||
"colours": "ppppreeeeesss",
|
||||
@@ -295,7 +295,7 @@ config = {
|
||||
"Description",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"LevelName",
|
||||
"StoreyName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
@@ -305,7 +305,7 @@ config = {
|
||||
"NetFloorArea",
|
||||
],
|
||||
"colours": "ppprreeess",
|
||||
"sort": [{"name": "LevelName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||
"sort": [{"name": "StoreyName", "order": "ASC"}, {"name": "Name", "order": "ASC"}],
|
||||
"get_category_elements": get_spaces,
|
||||
"get_element_data": get_space_data,
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcFM. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.fm
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.system
|
||||
|
||||
+42
-7
@@ -549,6 +549,36 @@ IfcSchema::IfcRelVoidsElement::list::ptr IfcGeom::Kernel::find_openings(IfcSchem
|
||||
return openings;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
IfcSchema::IfcMaterial* get_single_from_aggregate(bool take_first_regardless_of_size, const T& agg) {
|
||||
if (take_first_regardless_of_size ? agg->size() >= 1 : agg->size() == 1) {
|
||||
auto* layer_or_profile = *agg->begin();
|
||||
if (layer_or_profile->Material()) {
|
||||
return layer_or_profile->Material();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef SCHEMA_HAS_IfcMaterialProfileSet
|
||||
IfcSchema::IfcMaterial* get_single_from_set(bool take_first_regardless_of_size, IfcSchema::IfcMaterialProfileSet* profileset) {
|
||||
return get_single_from_aggregate(take_first_regardless_of_size, profileset->MaterialProfiles());
|
||||
}
|
||||
#endif
|
||||
IfcSchema::IfcMaterial* get_single_from_set(bool take_first_regardless_of_size, IfcSchema::IfcMaterialLayerSet* profileset) {
|
||||
return get_single_from_aggregate(take_first_regardless_of_size, profileset->MaterialLayers());
|
||||
}
|
||||
|
||||
IfcSchema::IfcMaterial* get_single_from_usage(bool take_first_regardless_of_size, IfcSchema::IfcMaterialLayerSetUsage* usage) {
|
||||
return get_single_from_set(take_first_regardless_of_size, usage->ForLayerSet());
|
||||
}
|
||||
#ifdef SCHEMA_HAS_IfcMaterialProfileSet
|
||||
IfcSchema::IfcMaterial* get_single_from_usage(bool take_first_regardless_of_size, IfcSchema::IfcMaterialProfileSetUsage* usage) {
|
||||
return get_single_from_set(take_first_regardless_of_size, usage->ForProfileSet());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const IfcSchema::IfcMaterial* IfcGeom::Kernel::get_single_material_association(const IfcSchema::IfcProduct* product) {
|
||||
IfcSchema::IfcMaterial* single_material = 0;
|
||||
IfcSchema::IfcRelAssociatesMaterial::list::ptr associated_materials = product->HasAssociations()->as<IfcSchema::IfcRelAssociatesMaterial>();
|
||||
@@ -566,14 +596,19 @@ const IfcSchema::IfcMaterial* IfcGeom::Kernel::get_single_material_association(c
|
||||
single_material = associated_material->as<IfcSchema::IfcMaterial>();
|
||||
// NB: IfcMaterialLayerSets are also considered, regardless of --enable-layerset-slicing. Picking
|
||||
// the first material (in accordance with other viewers) when layerset-slicing is disabled.
|
||||
if (!single_material && associated_material->as<IfcSchema::IfcMaterialLayerSetUsage>()) {
|
||||
IfcSchema::IfcMaterialLayerSet* layerset = associated_material->as<IfcSchema::IfcMaterialLayerSetUsage>()->ForLayerSet();
|
||||
if (getValue(GV_LAYERSET_FIRST) > 0.0 ? layerset->MaterialLayers()->size() >= 1 : layerset->MaterialLayers()->size() == 1) {
|
||||
IfcSchema::IfcMaterialLayer* layer = (*layerset->MaterialLayers()->begin());
|
||||
if (layer->Material()) {
|
||||
single_material = layer->Material();
|
||||
}
|
||||
if (!single_material) {
|
||||
if (auto* m = associated_material->as<IfcSchema::IfcMaterialLayerSetUsage>()) {
|
||||
single_material = get_single_from_usage(getValue(GV_LAYERSET_FIRST) > 0.0, m);
|
||||
} else if (auto* m = associated_material->as<IfcSchema::IfcMaterialLayerSet>()) {
|
||||
single_material = get_single_from_set(getValue(GV_LAYERSET_FIRST) > 0.0, m);
|
||||
}
|
||||
#ifdef SCHEMA_HAS_IfcMaterialProfileSet
|
||||
else if (auto* m = associated_material->as<IfcSchema::IfcMaterialProfileSetUsage>()) {
|
||||
single_material = get_single_from_usage(getValue(GV_LAYERSET_FIRST) > 0.0, m);
|
||||
} else if (auto* m = associated_material->as<IfcSchema::IfcMaterialProfileSet>()) {
|
||||
single_material = get_single_from_set(getValue(GV_LAYERSET_FIRST) > 0.0, m);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
Python API Reference
|
||||
====================
|
||||
|
||||
This page contains auto-generated API reference documentation [#f1]_.
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
{% for page in pages %}
|
||||
{% if page.top_level_object and page.display %}
|
||||
{{ page.include_path }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
.. [#f1] Created with `sphinx-autoapi <https://github.com/readthedocs/sphinx-autoapi>`_
|
||||
@@ -0,0 +1,114 @@
|
||||
{% if not obj.display %}
|
||||
:orphan:
|
||||
|
||||
{% endif %}
|
||||
:py:mod:`{{ obj.name }}`
|
||||
=========={{ "=" * obj.name|length }}
|
||||
|
||||
.. py:module:: {{ obj.name }}
|
||||
|
||||
{% if obj.docstring %}
|
||||
.. autoapi-nested-parse::
|
||||
|
||||
{{ obj.docstring|indent(3) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% block subpackages %}
|
||||
{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
|
||||
{% if visible_subpackages %}
|
||||
Subpackages
|
||||
-----------
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
{% for subpackage in visible_subpackages %}
|
||||
{{ subpackage.short_name }}/index.rst
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block submodules %}
|
||||
{% set visible_submodules = obj.submodules|selectattr("display")|list %}
|
||||
{% if visible_submodules %}
|
||||
Submodules
|
||||
----------
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
{% for submodule in visible_submodules %}
|
||||
{{ submodule.short_name }}/index.rst
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if obj.all is not none %}
|
||||
{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %}
|
||||
{% elif obj.type is equalto("package") %}
|
||||
{% set visible_children = obj.children|selectattr("display")|list %}
|
||||
{% else %}
|
||||
{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %}
|
||||
{% endif %}
|
||||
{% if visible_children %}
|
||||
{{ obj.type|title }} Contents
|
||||
{{ "-" * obj.type|length }}---------
|
||||
|
||||
{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %}
|
||||
{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %}
|
||||
{% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %}
|
||||
{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %}
|
||||
{% block classes scoped %}
|
||||
{% if visible_classes %}
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for klass in visible_classes %}
|
||||
{{ klass.id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block functions scoped %}
|
||||
{% if visible_functions %}
|
||||
Functions
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for function in visible_functions %}
|
||||
{{ function.id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block attributes scoped %}
|
||||
{% if visible_attributes %}
|
||||
Attributes
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoapisummary::
|
||||
|
||||
{% for attribute in visible_attributes %}
|
||||
{{ attribute.id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% for obj_item in visible_children %}
|
||||
{{ obj_item.render()|indent(0) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
+26
-4
@@ -8,6 +8,9 @@ h1, h2, h3, h4 {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
h1 code.literal {
|
||||
background: none;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -16,6 +19,7 @@ a {
|
||||
}
|
||||
.sidebar-brand-text {
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.blockbutton {
|
||||
max-width: 500px;
|
||||
@@ -47,14 +51,32 @@ section img {
|
||||
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Make it clearer which signatures are part of a class */
|
||||
.py.class {
|
||||
/* Make it clearer which signatures are part of a class */
|
||||
border-left: 3px solid var(--color-brand-primary);
|
||||
}
|
||||
.py.function, .py.method {
|
||||
/* Make it clearer which signatures are part of a method or function */
|
||||
border-left: 3px solid var(--color-background-item);
|
||||
.py.class > .sig {
|
||||
background: var(--color-brand-primary) !important;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.py.class > .sig * {
|
||||
color: #2e3436 !important;
|
||||
}
|
||||
.py.class > .sig a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Make it easier to spot functions and methods */
|
||||
.py.function, .py.method {
|
||||
border-top: 1px solid var(--color-background-item);
|
||||
}
|
||||
dl.py.property, dl.py.attribute, dl.py.method, dl.py.function {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.field-list > dt {
|
||||
/* Clearly distinguish parameters otherwise it looks like a wall of text */
|
||||
color: var(--color-brand-content);
|
||||
|
||||
@@ -74,6 +74,9 @@ autoapi_dirs = ['../ifcopenshell', '../../bcf/src', '../../bsdd', '../../ifccsv'
|
||||
# These are auto-generated based on the IFC schema, so exclude them
|
||||
autoapi_ignore = ['*ifcopenshell/express/rules*']
|
||||
|
||||
# Custom autoapi templates to make it easier to read our docs
|
||||
autoapi_template_dir = "_autoapi_templates"
|
||||
|
||||
# autoapi_options doesn't have show-module-summary, as it tends to create one
|
||||
# page per function which contradicts the presentation of showing all functions
|
||||
# as a list. This creates two possible locations where a function is documented
|
||||
@@ -81,7 +84,7 @@ autoapi_ignore = ['*ifcopenshell/express/rules*']
|
||||
# ifcopenshell.file is imported from ifcopenshell.file.file, but it gets pretty
|
||||
# confusing to see the docs again in multiple places (seriously,
|
||||
# ifcopenshell.file.file is everywhere).
|
||||
autoapi_options = ['members', 'undoc-members', 'private-members', 'special-members', 'show-inheritance']
|
||||
autoapi_options = ['members', 'undoc-members', 'show-inheritance', 'imported-members']
|
||||
|
||||
# This option is set to both to allow both class docstrings and __init__ docstrings.
|
||||
autoapi_python_class_content = 'both'
|
||||
@@ -130,7 +133,10 @@ html_theme_options = {
|
||||
"color-background-border": "#cfd0cb",
|
||||
"color-foreground-primary": "#2e3436",
|
||||
"color-sidebar-item-background--hover": "#f7f7f6",
|
||||
"color-link": "#39b54a",
|
||||
"color-link--visited": "#39b54a",
|
||||
"color-link--hover": "#d98014",
|
||||
"color-link--visited--hover": "#d98014",
|
||||
"font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
|
||||
},
|
||||
"dark_css_variables": {
|
||||
@@ -141,7 +147,10 @@ html_theme_options = {
|
||||
"color-background-border": "#2e3436",
|
||||
"color-foreground-primary": "#eeeeec",
|
||||
"color-sidebar-item-background--hover": "#2e3436",
|
||||
"color-link": "#39b54a",
|
||||
"color-link--visited": "#39b54a",
|
||||
"color-link--hover": "#d98014",
|
||||
"color-link--visited--hover": "#d98014",
|
||||
"font-stack": "Nunito, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
|
||||
},
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ the following comparison checks:
|
||||
"``>=``", "Must be greater than or equal to the value."
|
||||
"``<``", "Must be less than the value."
|
||||
"``<=``", "Must be less than or equal to the value."
|
||||
"``*=``", "Must contain the value."
|
||||
"``!*=``", "Must not contain the value."
|
||||
|
||||
When you specify a ``{{pset}}``, ``{{prop}}``, or ``{{value}}``, there are
|
||||
three ways you can do so:
|
||||
@@ -184,6 +186,7 @@ Valid keys are:
|
||||
"``material`` or ``mat``", "Gets the assigned material, which may be a material set."
|
||||
"``item`` or ``i``", "If the previous key returns a material set, gets the relevant material set items"
|
||||
"``materials`` or ``mats``", "Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element"
|
||||
"``profiles``", "Gets a list of IfcProfileDefs assigned (such as via a material profile) or used (such as in an extrusion) in the element"
|
||||
"``x``", "Gets the X coordinate of the element's placement"
|
||||
"``y``", "Gets the Y coordinate of the element's placement"
|
||||
"``z``", "Gets the Z coordinate of the element's placement"
|
||||
|
||||
@@ -21,14 +21,15 @@ Python API documentation is autogenerated from docstrings present in the source
|
||||
code of the respective Python module.
|
||||
|
||||
If you want to build the documentation locally, the documentation system uses
|
||||
`Sphinx <https://www.sphinx-doc.org/en/master/>`_. First, install the theme and
|
||||
theme dependencies:
|
||||
`Sphinx <https://www.sphinx-doc.org/en/master/>`_. First, install Sphinx and
|
||||
dependencies:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install furo
|
||||
$ pip install sphinx
|
||||
$ pip install sphinx-autoapi
|
||||
$ pip install sphinx-copybutton
|
||||
$ pip install furo
|
||||
|
||||
Now you can generate the documentation:
|
||||
|
||||
|
||||
@@ -16,32 +16,51 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""The entry module for IfcOpenShell
|
||||
"""Welcome to IfcOpenShell! IfcOpenShell provides a way to read and write IFCs.
|
||||
|
||||
Typically used for opening an IFC via a filepath, or accessing one of the
|
||||
submodules.
|
||||
IfcOpenShell can open IFC files, read entities (such as walls, buildings,
|
||||
properties, systems, etc), edit attributes, write out ``.ifc`` files and more.
|
||||
|
||||
This module provides primitive functions to interact with IFC, including:
|
||||
|
||||
- For most users, you can open and read IFC models, see docs for :func:`open`.
|
||||
This returns an :class:`file` object representing the IFC model. You can then
|
||||
query the model to filter elements.
|
||||
- For developers, you can query the schema itself, see docs for
|
||||
:func:`schema_by_name`. This returns a schema object which you can use to
|
||||
analyse the definitions of IFC classes and data types.
|
||||
|
||||
You may also be interested in:
|
||||
|
||||
- For model authoring and editing operations, see :mod:`ifcopenshell.api`.
|
||||
- For extracting information from models, see :mod:`ifcopenshell.util`.
|
||||
- For processing geometry, see :mod:`ifcopenshell.geom`.
|
||||
|
||||
|
||||
For more details, consult https://docs.ifcopenshell.org/
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import ifcopenshell
|
||||
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
||||
model = ifcopenshell.open("/path/to/model.ifc")
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
||||
|
||||
model = ifcopenshell.open("/path/to/model.ifc")
|
||||
walls = model.by_type("IfcWall")
|
||||
|
||||
for wall in walls:
|
||||
print(wall.Name)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.util.file
|
||||
|
||||
if hasattr(os, "uname"):
|
||||
platform_system = os.uname()[0].lower()
|
||||
@@ -60,22 +79,29 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "lib", p
|
||||
|
||||
try:
|
||||
from . import ifcopenshell_wrapper
|
||||
except Exception as e:
|
||||
if int(python_version_tuple[0]) == 2:
|
||||
# Only for py2, as py3 has exception chaining
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print("-" * 64)
|
||||
except Exception:
|
||||
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
||||
|
||||
from . import guid
|
||||
from .file import file
|
||||
from .entity_instance import entity_instance, register_schema_attributes
|
||||
from .sql import sqlite, sqlite_entity
|
||||
|
||||
# explicitly specify available imported symbols
|
||||
# (it's a requirement for a typed library)
|
||||
__all__ = [
|
||||
"ifcopenshell_wrapper",
|
||||
"file",
|
||||
"entity_instance",
|
||||
"sqlite",
|
||||
"sqlite_entity",
|
||||
"stream",
|
||||
"stream_entity",
|
||||
]
|
||||
|
||||
try:
|
||||
from .stream import stream, stream_entity
|
||||
except: pass
|
||||
except:
|
||||
pass
|
||||
|
||||
READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR
|
||||
NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER
|
||||
@@ -84,19 +110,22 @@ UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA
|
||||
|
||||
class Error(Exception):
|
||||
"""Error used when a generic problem occurs"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SchemaError(Error):
|
||||
"""Error used when an IFC schema related problem occurs"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def open(path: "os.PathLike | str", format: str = None, should_stream: bool = False) -> file:
|
||||
def open(path: Union[os.PathLike, str], format: Optional[str] = None, should_stream: bool = False) -> file:
|
||||
"""Loads an IFC dataset from a filepath
|
||||
|
||||
You can specify a file format. If no format is given, it is guessed from its extension.
|
||||
Currently supported specified format : .ifc | .ifcZIP | .ifcXML
|
||||
You can specify a file format. If no format is given, it is guessed from
|
||||
its extension. Currently supported specified format: .ifc | .ifcZIP |
|
||||
.ifcXML.
|
||||
|
||||
You can then filter by element ID, class, etc, and subscript by id or guid.
|
||||
|
||||
@@ -114,7 +143,7 @@ def open(path: "os.PathLike | str", format: str = None, should_stream: bool = Fa
|
||||
"""
|
||||
path = Path(path)
|
||||
if format is None:
|
||||
format = ifcopenshell.util.file.guess_format(path)
|
||||
format = guess_format(path)
|
||||
if format == ".ifcXML":
|
||||
f = ifcopenshell_wrapper.parse_ifcxml(str(path.absolute()))
|
||||
if f:
|
||||
@@ -141,8 +170,7 @@ def open(path: "os.PathLike | str", format: str = None, should_stream: bool = Fa
|
||||
NO_HEADER: (Error, "Unable to parse IFC SPF header"),
|
||||
UNSUPPORTED_SCHEMA: (
|
||||
SchemaError,
|
||||
"Unsupported schema: %s"
|
||||
% ",".join(f.header.file_schema.schema_identifiers),
|
||||
"Unsupported schema: %s" % ",".join(f.header.file_schema.schema_identifiers),
|
||||
),
|
||||
}[f.good().value()]
|
||||
raise exc(msg)
|
||||
@@ -152,7 +180,7 @@ def create_entity(type, schema="IFC4", *args, **kwargs):
|
||||
"""Creates a new IFC entity that does not belong to an IFC file object
|
||||
|
||||
Note that it is more common to create entities within a existing file
|
||||
object. See :meth:`ifcopenshell.file.file.create_entity`.
|
||||
object. See :meth:`ifcopenshell.file.create_entity`.
|
||||
|
||||
:param type: Case insensitive name of the IFC class
|
||||
:type type: string
|
||||
@@ -161,7 +189,7 @@ def create_entity(type, schema="IFC4", *args, **kwargs):
|
||||
:param args: The positional arguments of the IFC class
|
||||
:param kwargs: The keyword arguments of the IFC class
|
||||
:returns: An entity instance
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
@@ -226,4 +254,35 @@ def schema_by_name(
|
||||
return ifcopenshell_wrapper.schema_by_name(schema)
|
||||
|
||||
|
||||
from .main import *
|
||||
def guess_format(path: Path) -> Union[str | None]:
|
||||
"""Guesses the IFC format using file extension
|
||||
|
||||
IFCs may be serialised as different formats. The most common is a ``.ifc``
|
||||
file, which is plaintext and stores data using the STEP Physical File
|
||||
format. IFC can also be stored as a Zipfile, XML, JSON, or SQL.
|
||||
|
||||
This will return the canonical form of the format. For example, if a path
|
||||
has the extension of .xml or .ifcxml (case insensitive), it will return
|
||||
.ifcXML.
|
||||
|
||||
Users generally won't call this function. The :func:`open` function uses
|
||||
this internally to guess the file format.
|
||||
|
||||
:return: Either .ifc, .ifcZIP, .ifcXML, .ifcJSON, .ifcSQLite, or None.
|
||||
"""
|
||||
suffix = path.suffix.lower()
|
||||
if suffix == ".ifc":
|
||||
return ".ifc"
|
||||
elif suffix in (".ifczip", ".zip"):
|
||||
return ".ifcZIP"
|
||||
elif suffix in (".ifcxml", ".xml"):
|
||||
return ".ifcXML"
|
||||
elif suffix in (".ifcjson", ".json"):
|
||||
return ".ifcJSON"
|
||||
elif suffix in (".ifcsqlite", ".sqlite", ".db"):
|
||||
return ".ifcSQLite"
|
||||
return None
|
||||
|
||||
|
||||
version = ifcopenshell_wrapper.version()
|
||||
get_log = ifcopenshell_wrapper.get_log
|
||||
|
||||
@@ -16,20 +16,39 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""High level user-oriented IFC authoring capabilities"""
|
||||
"""High level IFC authoring and editing functions
|
||||
|
||||
Authoring, editing, and deleting IFC data requires a detailed understanding of
|
||||
the rules of the IFC schema. This API module provides simple to use authoring
|
||||
functions that hide this complexity from you. Things like managing differences
|
||||
between IFC versions, tracking owernship changes, or cleaning up after orphaned
|
||||
relationships are all handled automatically.
|
||||
|
||||
If you're new to IFC authoring, start by looking at the following APIs:
|
||||
|
||||
- See :func:`ifcopenshell.api.project.create_file` to create a new IFC.
|
||||
- See :func:`ifcopenshell.api.root.create_entity` to create new entities, like
|
||||
the mandatory IfcProject, and then an IfcSite, IfcWall, etc.
|
||||
- See :func:`ifcopenshell.api.aggregate.assign_object` to create a spatial
|
||||
hierarchy.
|
||||
- See :func:`ifcopenshell.api.spatial.assign_container` to place physical
|
||||
elements (e.g. walls) inside spatial elements (e.g. building storeys).
|
||||
|
||||
Also see how to `create a simple model from scratch
|
||||
<https://docs.ifcopenshell.org/ifcopenshell-python/code_examples.html#create-a-simple-model-from-scratch>`_.
|
||||
"""
|
||||
|
||||
import json
|
||||
import numpy
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from typing import Callable, Any, Optional
|
||||
from functools import partial
|
||||
|
||||
|
||||
pre_listeners = {}
|
||||
post_listeners = {}
|
||||
pre_listeners: dict[str, dict] = {}
|
||||
post_listeners: dict[str, dict] = {}
|
||||
|
||||
|
||||
def batching_argument_deprecation(
|
||||
@@ -127,8 +146,8 @@ ARGUMENTS_DEPRECATION = {
|
||||
}
|
||||
|
||||
|
||||
CACHED_USECASE_CLASSES = {}
|
||||
CACHED_USECASES = {}
|
||||
CACHED_USECASE_CLASSES: dict[str, Callable] = {}
|
||||
CACHED_USECASES: dict[str, Callable] = {}
|
||||
|
||||
|
||||
def run(
|
||||
@@ -151,34 +170,6 @@ def run(
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
# see #4531
|
||||
if usecase_path in ARGUMENTS_DEPRECATION:
|
||||
usecase_path, settings = ARGUMENTS_DEPRECATION[usecase_path](usecase_path, settings)
|
||||
|
||||
# TODO: settings serialization for client-server systems
|
||||
# def serialise_entity_instance(entity):
|
||||
# return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
# vcs_settings = settings.copy()
|
||||
# for key, value in settings.items():
|
||||
# if isinstance(value, ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = serialise_entity_instance(value)
|
||||
# elif isinstance(value, numpy.ndarray):
|
||||
# vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
# elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
if "add_representation" in usecase_path:
|
||||
pass
|
||||
# print(usecase_path, "{ ... settings too complex right now ... }")
|
||||
elif "owner." in usecase_path:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
# print(vcs_settings)
|
||||
# try:
|
||||
# print(usecase_path, json.dumps(vcs_settings))
|
||||
# except:
|
||||
# print(usecase_path, vcs_settings)
|
||||
|
||||
usecase_class = CACHED_USECASE_CLASSES.get(usecase_path)
|
||||
if usecase_class is None:
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
@@ -247,11 +238,8 @@ def remove_all_listeners():
|
||||
|
||||
def extract_docs(module, usecase):
|
||||
import typing
|
||||
import inspect
|
||||
import collections
|
||||
|
||||
results = []
|
||||
|
||||
inputs = collections.OrderedDict()
|
||||
|
||||
function_init = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.__init__
|
||||
@@ -295,67 +283,75 @@ def extract_docs(module, usecase):
|
||||
return node_data
|
||||
|
||||
|
||||
def _wrap_api(init_globals, file, package):
|
||||
"""API endpoints are implemented as Usecase classes. This wraps the classes as functions.
|
||||
|
||||
Calling classes is syntactically awkward. For example,
|
||||
ifcopenshell.api.root.create_entity.Usecase(f).execute().
|
||||
It is more elegant to call it using ifcopenshell.api.root.create_entity(f).
|
||||
|
||||
Calling _wrap_api from an API package's __init__.py will generate these
|
||||
wrapper functions at runtime.
|
||||
"""
|
||||
import pkgutil
|
||||
import importlib
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
|
||||
def _create_function(module_name, Usecase):
|
||||
"""Create a function that wraps the Usecase class's execute method."""
|
||||
usecase_path = ".".join(Usecase.__module__.split(".")[-2:])
|
||||
|
||||
def wrapper(*args, should_run_listeners: bool = True, **settings):
|
||||
ifc_file = args[0] if args else None
|
||||
if should_run_listeners:
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
def serialise_settings(settings):
|
||||
def serialise_entity_instance(entity):
|
||||
return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
|
||||
vcs_settings = settings.copy()
|
||||
for key, value in settings.items():
|
||||
if isinstance(value, ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = serialise_entity_instance(value)
|
||||
elif isinstance(value, numpy.ndarray):
|
||||
vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
else:
|
||||
try:
|
||||
usecase = Usecase(*args, **settings)
|
||||
except TypeError as e:
|
||||
msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(Usecase.__init__)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation."
|
||||
raise TypeError(msg) from e
|
||||
vcs_settings[key] = str(value)
|
||||
except:
|
||||
vcs_settings[key] = "n/a"
|
||||
try:
|
||||
return json.dumps(vcs_settings)
|
||||
except:
|
||||
return str(vcs_settings)
|
||||
|
||||
result = usecase.execute()
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in post_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
def wrap_usecase(usecase_path, usecase):
|
||||
"""Wraps an API function in pre/post listeners."""
|
||||
|
||||
return result
|
||||
def wrapper(*args, should_run_listeners: bool = True, **settings):
|
||||
ifc_file = args[0] if args else None
|
||||
nonlocal usecase_path
|
||||
if should_run_listeners:
|
||||
listeners = list(pre_listeners.get(usecase_path, {}).values())
|
||||
listeners += pre_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
wrapper.__signature__ = inspect.signature(Usecase.__init__)
|
||||
wrapper.__doc__ = Usecase.__init__.__doc__
|
||||
wrapper.__name__ = module_name
|
||||
return wrapper
|
||||
# see #4531
|
||||
if usecase_path in ARGUMENTS_DEPRECATION:
|
||||
usecase_path, settings = ARGUMENTS_DEPRECATION[usecase_path](usecase_path, settings)
|
||||
|
||||
for finder, name, ispkg in pkgutil.iter_modules([Path(file).parent]):
|
||||
try:
|
||||
module = importlib.import_module(f".{name}", package)
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: {package}.{name} - {e}")
|
||||
continue
|
||||
usecase_cls = getattr(module, "Usecase", None)
|
||||
if usecase_cls:
|
||||
func = _create_function(name, usecase_cls)
|
||||
init_globals[name] = func
|
||||
result = usecase(*args, **settings)
|
||||
except TypeError as e:
|
||||
msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(usecase)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation."
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if should_run_listeners:
|
||||
listeners = list(post_listeners.get(usecase_path, {}).values())
|
||||
listeners += post_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
return result
|
||||
|
||||
wrapper.__signature__ = inspect.signature(usecase)
|
||||
wrapper.__doc__ = usecase.__doc__
|
||||
wrapper.__name__ = usecase_path
|
||||
return wrapper
|
||||
|
||||
|
||||
# Expose all submodules. This means that the user can just type `import ifcopenshell.api`.
|
||||
for loader, module_name, is_pkg in pkgutil.iter_modules(__path__, __name__ + "."):
|
||||
module = importlib.import_module(module_name)
|
||||
def wrap_usecases(path, name):
|
||||
"""This developer feature wraps an API module's usecases with listeners."""
|
||||
import sys
|
||||
import pkgutil
|
||||
|
||||
# Check if it's a direct child (only one level deep)
|
||||
if module_name.count(".") == __name__.count(".") + 1:
|
||||
# Generate wrapper functions for each usecase
|
||||
_wrap_api(vars(module), module.__file__, module.__name__)
|
||||
module_name = name.split(".")[-1]
|
||||
module = sys.modules[name]
|
||||
for loader, usecase_name, is_pkg in pkgutil.iter_modules(path):
|
||||
# We may not be able to get the usecase if we are missing a dependency.
|
||||
usecase = getattr(module, usecase_name, None)
|
||||
if callable(usecase):
|
||||
usecase_path = f"{module_name}.{usecase_name}"
|
||||
setattr(module, usecase_name, wrap_usecase(usecase_path, usecase))
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Aggregates are the concept of breaking down larger wholes into smaller parts.
|
||||
"""Aggregates is the concept of breaking down larger wholes into smaller parts.
|
||||
|
||||
One common use is spatial elements, such as how a site has multiple buildings,
|
||||
and a building has multiple storeys. Another is for regular elements, such as
|
||||
how a wall is made out of members and coverings.
|
||||
For example, spatial elements such as sites are broken down into one or more
|
||||
buildings, and a building is broken down into storeys. Another example is for
|
||||
physical elements, such as how a wall is made out of members and coverings.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_object import assign_object
|
||||
from .unassign_object import unassign_object
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -18,153 +18,150 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
relating_object: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns object as an aggregate to the products
|
||||
def assign_object(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
relating_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns object as an aggregate to the products
|
||||
|
||||
All physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", where large things are made up of
|
||||
smaller things. This tree always begins at an "IfcProject" and is then
|
||||
broken down using "decomposition" relationships, of which aggregation is
|
||||
the first relationship you will use.
|
||||
All physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", where large things are made up of
|
||||
smaller things. This tree always begins at an "IfcProject" and is then
|
||||
broken down using "decomposition" relationships, of which aggregation is
|
||||
the first relationship you will use.
|
||||
|
||||
Typically used when you want to describe how large spaces are made up of
|
||||
smaller spaces. For example large spatial elements (e.g. sites,
|
||||
buidings) can be made out of smaller spatial elements (e.g. storeys,
|
||||
spaces).
|
||||
Typically used when you want to describe how large spaces are made up of
|
||||
smaller spaces. For example large spatial elements (e.g. sites,
|
||||
buidings) can be made out of smaller spatial elements (e.g. storeys,
|
||||
spaces).
|
||||
|
||||
The largest space (typically the IfcSite) can then be aggregated in a
|
||||
project. It is requirement for all spatial structures to be directly or
|
||||
indirectly aggregated back to the IfcProject to create a hierarchy of
|
||||
spaces.
|
||||
The largest space (typically the IfcSite) can then be aggregated in a
|
||||
project. It is requirement for all spatial structures to be directly or
|
||||
indirectly aggregated back to the IfcProject to create a hierarchy of
|
||||
spaces.
|
||||
|
||||
The other common usecase is when larger physical products are made up of
|
||||
smaller physical products. For example, a stair might be made out of a
|
||||
flight, a landing, a railing and so on. Or a wall might be made out of
|
||||
stud members, and coverings.
|
||||
The other common usecase is when larger physical products are made up of
|
||||
smaller physical products. For example, a stair might be made out of a
|
||||
flight, a landing, a railing and so on. Or a wall might be made out of
|
||||
stud members, and coverings.
|
||||
|
||||
As a product may only have a single location in the "spatial
|
||||
decomposition" tree, assigning an aggregate relationship will remove any
|
||||
previous aggregation, containment, or nesting relationships it may have.
|
||||
As a product may only have a single location in the "spatial
|
||||
decomposition" tree, assigning an aggregate relationship will remove any
|
||||
previous aggregation, containment, or nesting relationships it may have.
|
||||
|
||||
IFC placements follow a convention where the placement is relative to
|
||||
its parent in the spatial hierarchy. If your product has a placement,
|
||||
its placement will be recalculated to follow this convention.
|
||||
IFC placements follow a convention where the placement is relative to
|
||||
its parent in the spatial hierarchy. If your product has a placement,
|
||||
its placement will be recalculated to follow this convention.
|
||||
|
||||
:param products: The list of parts of the aggregate, typically of IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param relating_object: The whole of the aggregate, typically an
|
||||
IfcElement or IfcSpatialStructureElement subclass
|
||||
:type relating_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
||||
:param products: The list of parts of the aggregate, typically of IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param relating_object: The whole of the aggregate, typically an
|
||||
IfcElement or IfcSpatialStructureElement subclass
|
||||
:type relating_object: ifcopenshell.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
|
||||
# The project contains a site (note that project aggregation is a special case in IFC)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
|
||||
# The project contains a site (note that project aggregation is a special case in IFC)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
|
||||
|
||||
# The site has a building
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"relating_object": relating_object,
|
||||
}
|
||||
# The site has a building
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
|
||||
"""
|
||||
settings = {
|
||||
"products": products,
|
||||
"relating_object": relating_object,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if not self.settings["products"]:
|
||||
return
|
||||
if not settings["products"]:
|
||||
return
|
||||
|
||||
products = set(self.settings["products"])
|
||||
relating_object = self.settings["relating_object"]
|
||||
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
|
||||
products = set(settings["products"])
|
||||
relating_object = settings["relating_object"]
|
||||
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
|
||||
|
||||
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products_without_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
products_with_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products_without_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
products_with_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
# check if there is anything to change
|
||||
for product in products:
|
||||
product_rel = next(iter(product.Decomposes), None)
|
||||
# check if there is anything to change
|
||||
for product in products:
|
||||
product_rel = next(iter(product.Decomposes), None)
|
||||
|
||||
if product_rel is None:
|
||||
products_without_aggregates.append(product)
|
||||
continue
|
||||
if product_rel is None:
|
||||
products_without_aggregates.append(product)
|
||||
continue
|
||||
|
||||
# either is_decomposed_by is None or product is part of different rel
|
||||
if product_rel != is_decomposed_by:
|
||||
previous_aggregates_rels.add(product_rel)
|
||||
products_with_aggregates.append(product)
|
||||
# either is_decomposed_by is None or product is part of different rel
|
||||
if product_rel != is_decomposed_by:
|
||||
previous_aggregates_rels.add(product_rel)
|
||||
products_with_aggregates.append(product)
|
||||
|
||||
# products with already assigned aggregates will be skipped
|
||||
# products with already assigned aggregates will be skipped
|
||||
|
||||
products_to_change = products_without_aggregates + products_with_aggregates
|
||||
# nothing to change
|
||||
if not products_to_change:
|
||||
return is_decomposed_by
|
||||
products_to_change = products_without_aggregates + products_with_aggregates
|
||||
# nothing to change
|
||||
if not products_to_change:
|
||||
return is_decomposed_by
|
||||
|
||||
# can be either only aggregated or only contained at the same time
|
||||
# some product might not be able to have a container
|
||||
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
|
||||
ifcopenshell.api.run("spatial.unassign_container", self.file, products=possibly_contained_products)
|
||||
# can be either only aggregated or only contained at the same time
|
||||
# some product might not be able to have a container
|
||||
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
|
||||
ifcopenshell.api.run("spatial.unassign_container", file, products=possibly_contained_products)
|
||||
|
||||
# unassign elements from previous aggregates
|
||||
for decomposes in previous_aggregates_rels:
|
||||
related_objects = set(decomposes.RelatedObjects) - products
|
||||
if related_objects:
|
||||
decomposes.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": decomposes})
|
||||
else:
|
||||
history = decomposes.OwnerHistory
|
||||
self.file.remove(decomposes)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
|
||||
# assign elements to a new aggregate
|
||||
if is_decomposed_by:
|
||||
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by})
|
||||
# unassign elements from previous aggregates
|
||||
for decomposes in previous_aggregates_rels:
|
||||
related_objects = set(decomposes.RelatedObjects) - products
|
||||
if related_objects:
|
||||
decomposes.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": decomposes})
|
||||
else:
|
||||
is_decomposed_by = self.file.create_entity(
|
||||
"IfcRelAggregates",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": list(products),
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
history = decomposes.OwnerHistory
|
||||
file.remove(decomposes)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
# assign elements to a new aggregate
|
||||
if is_decomposed_by:
|
||||
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": is_decomposed_by})
|
||||
else:
|
||||
is_decomposed_by = file.create_entity(
|
||||
"IfcRelAggregates",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": list(products),
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
)
|
||||
|
||||
# localize placement relative to a new aggregate for affected products
|
||||
for product in products_to_change:
|
||||
placement = getattr(product, "ObjectPlacement", None)
|
||||
if placement and placement.is_a("IfcLocalPlacement"):
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
file,
|
||||
product=product,
|
||||
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
|
||||
is_si=False,
|
||||
)
|
||||
|
||||
# localize placement relative to a new aggregate for affected products
|
||||
for product in products_to_change:
|
||||
placement = getattr(product, "ObjectPlacement", None)
|
||||
if placement and placement.is_a("IfcLocalPlacement"):
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
self.file,
|
||||
product=product,
|
||||
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
|
||||
is_si=False,
|
||||
)
|
||||
|
||||
return is_decomposed_by
|
||||
return is_decomposed_by
|
||||
|
||||
@@ -21,60 +21,57 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]):
|
||||
"""Unassigns products from their aggregate
|
||||
def unassign_object(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None:
|
||||
"""Unassigns products from their aggregate
|
||||
|
||||
A product (i.e. a smaller part of a whole) may be aggregated into zero
|
||||
or one larger space or element. This function will remove that
|
||||
aggregation relationship.
|
||||
A product (i.e. a smaller part of a whole) may be aggregated into zero
|
||||
or one larger space or element. This function will remove that
|
||||
aggregation relationship.
|
||||
|
||||
As all physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", using this function will remove the
|
||||
product from that tree. This is a dangerous operation and may result in
|
||||
the product no longer being visible in IFC applications.
|
||||
As all physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", using this function will remove the
|
||||
product from that tree. This is a dangerous operation and may result in
|
||||
the product no longer being visible in IFC applications.
|
||||
|
||||
If the product is not part of an aggregation relationship, nothing will
|
||||
happen.
|
||||
If the product is not part of an aggregation relationship, nothing will
|
||||
happen.
|
||||
|
||||
:param products: The list of parts of the aggregate, typically of IfcElements or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param products: The list of parts of the aggregate, typically of IfcElements or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
|
||||
# nothing is returned
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
|
||||
# nothing is returned, relationship is removed
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"products": products}
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
|
||||
# nothing is returned
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
|
||||
# nothing is returned, relationship is removed
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
|
||||
"""
|
||||
settings = {"products": products}
|
||||
|
||||
def execute(self) -> None:
|
||||
products = set(self.settings["products"])
|
||||
rels = set(
|
||||
rel
|
||||
for product in products
|
||||
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
|
||||
)
|
||||
products = set(settings["products"])
|
||||
rels = set(
|
||||
rel
|
||||
for product in products
|
||||
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
|
||||
)
|
||||
|
||||
for rel in rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,3 +15,15 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Basic modification of the attributes of an element.
|
||||
|
||||
All IFC entities have attributes. Some of these attributes contain rules about
|
||||
inheritance and what they are allowed to contain. These usecases make sure that
|
||||
any editing complies with these rules.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .edit_attributes import edit_attributes
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,66 +17,53 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, product=None, attributes=None):
|
||||
"""Edit the attributes of a product
|
||||
def edit_attributes(file: ifcopenshell.file, product: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a product
|
||||
|
||||
All IFC entities have attributes. Normally they can be edited directly,
|
||||
by simply assigning a new value to them. In some scenarios, you may wish
|
||||
to also ensure that ownership history is updated. This function provides
|
||||
that convenience.
|
||||
All IFC entities have attributes. Normally they can be edited directly,
|
||||
by simply assigning a new value to them. In some scenarios, you may wish
|
||||
to also ensure that ownership history is updated. This function provides
|
||||
that convenience.
|
||||
|
||||
:param product: The product you want to edit. This may be any rooted IFC
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param product: The product you want to edit. This may be any rooted IFC
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
ifcopenshell.api.run("attribute.edit_attributes", model,
|
||||
product=element, attributes={"Name": "Waldo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"product": product, "attributes": attributes or {}}
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
ifcopenshell.api.run("attribute.edit_attributes", model,
|
||||
product=element, attributes={"Name": "Waldo"})
|
||||
"""
|
||||
settings = {"product": product, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["product"], name, value)
|
||||
if hasattr(self.settings["product"], "PredefinedType"):
|
||||
if hasattr(self.settings["product"], "ElementType"):
|
||||
if (
|
||||
self.settings["product"].ElementType is None
|
||||
and self.settings["product"].PredefinedType == "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif (
|
||||
self.settings["product"].ElementType
|
||||
and self.settings["product"].PredefinedType != "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "USERDEFINED"
|
||||
elif hasattr(self.settings["product"], "ObjectType"):
|
||||
relating_type = ifcopenshell.util.element.get_type(self.settings["product"])
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
|
||||
self.settings["product"].ObjectType = None
|
||||
self.settings["product"].PredefinedType = None
|
||||
elif (
|
||||
self.settings["product"].ObjectType is None
|
||||
and self.settings["product"].PredefinedType == "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif (
|
||||
self.settings["product"].ObjectType
|
||||
and self.settings["product"].PredefinedType != "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "USERDEFINED"
|
||||
if hasattr(self.settings["product"], "OwnerHistory"):
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": self.settings["product"]})
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["product"], name, value)
|
||||
if hasattr(settings["product"], "PredefinedType"):
|
||||
if hasattr(settings["product"], "ElementType"):
|
||||
if settings["product"].ElementType is None and settings["product"].PredefinedType == "USERDEFINED":
|
||||
settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif settings["product"].ElementType and settings["product"].PredefinedType != "USERDEFINED":
|
||||
settings["product"].PredefinedType = "USERDEFINED"
|
||||
elif hasattr(settings["product"], "ObjectType"):
|
||||
relating_type = ifcopenshell.util.element.get_type(settings["product"])
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
|
||||
settings["product"].ObjectType = None
|
||||
settings["product"].PredefinedType = None
|
||||
elif settings["product"].ObjectType is None and settings["product"].PredefinedType == "USERDEFINED":
|
||||
settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif settings["product"].ObjectType and settings["product"].PredefinedType != "USERDEFINED":
|
||||
settings["product"].PredefinedType = "USERDEFINED"
|
||||
if hasattr(settings["product"], "OwnerHistory"):
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": settings["product"]})
|
||||
|
||||
@@ -18,4 +18,15 @@
|
||||
|
||||
"""Boundaries are primarily used for representing virtual interfaces between
|
||||
spaces for energy analysis.
|
||||
|
||||
Boundaries may be associated with spaces or physical elements that enclose
|
||||
spaces such as walls, doors, and windows.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_connection_geometry import assign_connection_geometry
|
||||
from .copy_boundary import copy_boundary
|
||||
from .edit_attributes import edit_attributes
|
||||
from .remove_boundary import remove_boundary
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,70 +17,83 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def assign_connection_geometry(
|
||||
file: ifcopenshell.file,
|
||||
rel_space_boundary: ifcopenshell.entity_instance,
|
||||
outer_boundary: list[tuple[float, float]],
|
||||
location: tuple[float, float, float],
|
||||
axis: tuple[float, float, float],
|
||||
ref_direction: tuple[float, float, float],
|
||||
inner_boundaries: Optional[list[list[tuple[float, float]]]] = None,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Create and assign a connection geometry to a space boundary relationship
|
||||
|
||||
A space boundary may optionally have a plane that represents how that
|
||||
space is adjacent to another space, known as the connection geometry.
|
||||
You may specify this plane in terms of an outer boundary polyline, zero
|
||||
or more inner boundaries (such as for windows), and a positional matrix
|
||||
for the orientation of the plane.
|
||||
|
||||
:param rel_space_boundary: The space boundary relationship to assign the
|
||||
connection geometry to.
|
||||
:type rel_space_boundary: ifcopenshell.entity_instance
|
||||
:param outer_boundary: A list of 2D points representing an open
|
||||
polyline. The last point will connect to the first point. Each
|
||||
point is represented by an interable of 2 floats. The coordinates of
|
||||
the points are relative to the positional matrix arguments.
|
||||
:type outer_boundary: list[tuple[float, float]]
|
||||
:param inner_boundaries: A list of zero or more inner boundaries to use
|
||||
for the plane. Each boundary is represented by an open polyline, as
|
||||
defined by the outer_boundary argument.
|
||||
:type inner_boundaries: list[list[tuple[float, float]]], optional
|
||||
:param location: The local origin of the connection geometry, defined as
|
||||
an XYZ coordinate relative to the placement of the space that is
|
||||
being bounded.
|
||||
:type location: tuple[float, float, float]
|
||||
:param axis: The local X axis of the connection geometry, defined as an
|
||||
XYZ vector relative to the placement of the space that is being
|
||||
bounded.
|
||||
:type axis: tuple[float, float, float]
|
||||
:param ref_direction: The local Z axis of the connection geometry,
|
||||
defined as an XYZ vector relative to the placement of the space that
|
||||
is being bounded. The Y vector is automatically derived using the
|
||||
right hand rule.
|
||||
:type ref_direction: tuple[float, float, float]
|
||||
:param unit_scale: The unit scale as calculated by
|
||||
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
|
||||
will be automatically calculated for you.
|
||||
:type unit_scale: float, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", model,
|
||||
rel_space_boundary=element,
|
||||
outer_boundary=[(0., 0.), (1., 0.), (1., 1.), (0., 1.)],
|
||||
location=[0., 0., 0.], axis=[1., 0., 0.], ref_direction=[0., 0., 1.],
|
||||
)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.rel_space_boundary = rel_space_boundary
|
||||
usecase.outer_boundary = outer_boundary
|
||||
usecase.inner_boundaries = inner_boundaries or ()
|
||||
usecase.location = location
|
||||
usecase.axis = axis
|
||||
usecase.ref_direction = ref_direction
|
||||
usecase.unit_scale = unit_scale
|
||||
usecase.ifc_vertices = []
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, rel_space_boundary=None, outer_boundary=None, inner_boundaries=None, location=None, axis=None, ref_direction=None, unit_scale=None):
|
||||
"""Create and assign a connection geometry to a space boundary relationship
|
||||
|
||||
A space boundary may optionally have a plane that represents how that
|
||||
space is adjacent to another space, known as the connection geometry.
|
||||
You may specify this plane in terms of an outer boundary polyline, zero
|
||||
or more inner boundaries (such as for windows), and a positional matrix
|
||||
for the orientation of the plane.
|
||||
|
||||
:param rel_space_boundary: The space boundary relationship to assign the
|
||||
connection geometry to.
|
||||
:type rel_space_boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:param outer_boundary: A list of 2D points representing an open
|
||||
polyline. The last point will connect to the first point. Each
|
||||
point is represented by an interable of 2 floats. The coordinates of
|
||||
the points are relative to the positional matrix arguments.
|
||||
:type outer_boundary: list[list[float]]
|
||||
:param inner_boundaries: A list of zero or more inner boundaries to use
|
||||
for the plane. Each boundary is represented by an open polyline, as
|
||||
defined by the outer_boundary argument.
|
||||
:type inner_boundaries: list[list[list[float]]], optional
|
||||
:param location: The local origin of the connection geometry, defined as
|
||||
an XYZ coordinate relative to the placement of the space that is
|
||||
being bounded.
|
||||
:type location: list[float]
|
||||
:param axis: The local X axis of the connection geometry, defined as an
|
||||
XYZ vector relative to the placement of the space that is being
|
||||
bounded.
|
||||
:type axis: list[float]
|
||||
:param ref_direction: The local Z axis of the connection geometry,
|
||||
defined as an XYZ vector relative to the placement of the space that
|
||||
is being bounded. The Y vector is automatically derived using the
|
||||
right hand rule.
|
||||
:type ref_direction: list[float]
|
||||
:param unit_scale: The unit scale as calculated by
|
||||
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
|
||||
will be automatically calculated for you.
|
||||
:type unit_scale: float, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", model,
|
||||
rel_space_boundary=element,
|
||||
outer_boundary=[(0., 0.), (1., 0.), (1., 1.), (0., 1.)],
|
||||
location=[0., 0., 0.], axis=[1., 0., 0.], ref_direction=[0., 0., 1.],
|
||||
)
|
||||
"""
|
||||
self.file = file
|
||||
self.rel_space_boundary = rel_space_boundary
|
||||
self.outer_boundary = outer_boundary
|
||||
self.inner_boundaries = inner_boundaries or ()
|
||||
self.location = location
|
||||
self.axis = axis
|
||||
self.ref_direction = ref_direction
|
||||
self.unit_scale = unit_scale
|
||||
self.ifc_vertices = []
|
||||
|
||||
def execute(self):
|
||||
if self.unit_scale is None:
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -19,29 +19,26 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, boundary=None):
|
||||
"""Copies a space boundary
|
||||
def copy_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Copies a space boundary
|
||||
|
||||
:param boundary: The IfcRelSpaceBoundary you want to copy.
|
||||
:type boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param boundary: The IfcRelSpaceBoundary you want to copy.
|
||||
:type boundary: ifcopenshell.entity_instance
|
||||
:return: Duplicate of the IfcRelSpaceBoundary
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
|
||||
# And now we have two
|
||||
boundary_copy = ifcopenshell.api.run("boundary.copy_boundary", model, boundary=boundary)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"boundary": boundary}
|
||||
# And now we have two
|
||||
boundary_copy = ifcopenshell.api.run("boundary.copy_boundary", model, boundary=boundary)
|
||||
"""
|
||||
settings = {"boundary": boundary}
|
||||
|
||||
def execute(self):
|
||||
result = ifcopenshell.util.element.copy(self.file, self.settings["boundary"])
|
||||
if result.ConnectionGeometry:
|
||||
result.ConnectionGeometry = ifcopenshell.util.element.copy_deep(self.file, result.ConnectionGeometry)
|
||||
return result
|
||||
result = ifcopenshell.util.element.copy(file, settings["boundary"])
|
||||
if result.ConnectionGeometry:
|
||||
result.ConnectionGeometry = ifcopenshell.util.element.copy_deep(file, result.ConnectionGeometry)
|
||||
return result
|
||||
|
||||
@@ -15,47 +15,53 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, entity=None, relating_space=None, related_building_element=None, parent_boundary=None, corresponding_boundary=None):
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
def edit_attributes(
|
||||
file: ifcopenshell.file,
|
||||
entity: ifcopenshell.entity_instance,
|
||||
relating_space: ifcopenshell.entity_instance,
|
||||
related_building_element: ifcopenshell.entity_instance,
|
||||
parent_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
corresponding_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
|
||||
Currently this function is quite minimal and offers no advantage to
|
||||
manual assignment of the space boundary attributes.
|
||||
Currently this function is quite minimal and offers no advantage to
|
||||
manual assignment of the space boundary attributes.
|
||||
|
||||
:param entity: The IfcRelSpaceBoundary to modify
|
||||
:type entity: ifcopenshell.entity_instance.entity_instance
|
||||
:param relating_space: The IfcSpace or IfcExternalSpatialElement that
|
||||
the space boundary is related to.
|
||||
:type relating_space: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_building_element: The IfcElement that defines the
|
||||
boundary, typically an IfcWall.
|
||||
:type relating_space: ifcopenshell.entity_instance.entity_instance
|
||||
:param parent_boundary: A parent IfcRelSpaceBoundary, only provided if
|
||||
this is an inner boundary. This can apply to 1st and 2nd level
|
||||
boundaries.
|
||||
:type parent_boundary: ifcopenshell.entity_instance.entity_instance,
|
||||
optional
|
||||
:param corresponding_boundary: The other IfcRelSpaceBoundary on the
|
||||
other side of the related element. The pair together represents a
|
||||
thermal boundary. This only applies to 2nd level boundaries.
|
||||
:type corresponding_boundary: ifcopenshell.entity_instance.entity_instance,
|
||||
optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.file = file
|
||||
self.entity = entity
|
||||
self.relating_space = relating_space
|
||||
self.related_building_element = related_building_element
|
||||
self.parent_boundary = parent_boundary
|
||||
self.corresponding_boundary = corresponding_boundary
|
||||
:param entity: The IfcRelSpaceBoundary to modify
|
||||
:type entity: ifcopenshell.entity_instance
|
||||
:param relating_space: The IfcSpace or IfcExternalSpatialElement that
|
||||
the space boundary is related to.
|
||||
:type relating_space: ifcopenshell.entity_instance
|
||||
:param related_building_element: The IfcElement that defines the
|
||||
boundary, typically an IfcWall.
|
||||
:type relating_space: ifcopenshell.entity_instance
|
||||
:param parent_boundary: A parent IfcRelSpaceBoundary, only provided if
|
||||
this is an inner boundary. This can apply to 1st and 2nd level
|
||||
boundaries.
|
||||
:type parent_boundary: ifcopenshell.entity_instance,
|
||||
optional
|
||||
:param corresponding_boundary: The other IfcRelSpaceBoundary on the
|
||||
other side of the related element. The pair together represents a
|
||||
thermal boundary. This only applies to 2nd level boundaries.
|
||||
:type corresponding_boundary: ifcopenshell.entity_instance,
|
||||
optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
entity = entity
|
||||
relating_space = relating_space
|
||||
related_building_element = related_building_element
|
||||
parent_boundary = parent_boundary
|
||||
corresponding_boundary = corresponding_boundary
|
||||
|
||||
def execute(self):
|
||||
self.entity.RelatingSpace = self.relating_space
|
||||
self.entity.RelatedBuildingElement = self.related_building_element
|
||||
if hasattr(self.entity, "ParentBoundary"):
|
||||
self.entity.ParentBoundary = self.parent_boundary
|
||||
if hasattr(self.entity, "CorrespondingBoundary"):
|
||||
self.entity.CorrespondingBoundary = self.corresponding_boundary
|
||||
entity.RelatingSpace = relating_space
|
||||
entity.RelatedBuildingElement = related_building_element
|
||||
if hasattr(entity, "ParentBoundary"):
|
||||
entity.ParentBoundary = parent_boundary
|
||||
if hasattr(entity, "CorrespondingBoundary"):
|
||||
entity.CorrespondingBoundary = corresponding_boundary
|
||||
|
||||
@@ -20,36 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, boundary=None):
|
||||
"""Removes a space boundary
|
||||
def remove_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a space boundary
|
||||
|
||||
The relating space or related building element is untouched. Only the
|
||||
boundary and its connection geometry is removed.
|
||||
The relating space or related building element is untouched. Only the
|
||||
boundary and its connection geometry is removed.
|
||||
|
||||
:param boundary: The IfcRelSpaceBoundary you want to remove.
|
||||
:type boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param boundary: The IfcRelSpaceBoundary you want to remove.
|
||||
:type boundary: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("boundary.remove_boundary", model, boundary=boundary)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"boundary": boundary}
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("boundary.remove_boundary", model, boundary=boundary)
|
||||
"""
|
||||
settings = {"boundary": boundary}
|
||||
|
||||
def execute(self):
|
||||
geometry = self.settings["boundary"].ConnectionGeometry
|
||||
if geometry:
|
||||
self.settings["boundary"].ConnectionGeometry = None
|
||||
ifcopenshell.util.element.remove_deep2(self.file, geometry)
|
||||
history = self.settings["boundary"].OwnerHistory
|
||||
self.file.remove(self.settings["boundary"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
geometry = settings["boundary"].ConnectionGeometry
|
||||
if geometry:
|
||||
settings["boundary"].ConnectionGeometry = None
|
||||
ifcopenshell.util.element.remove_deep2(file, geometry)
|
||||
history = settings["boundary"].OwnerHistory
|
||||
file.remove(settings["boundary"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,3 +15,24 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Classification systems are a way of categorising objects
|
||||
|
||||
Although IFC itself comes with a built-in classification hierarchy (e.g.
|
||||
IfcWall and its predefined types of PARTITIONING, etc), there are many external
|
||||
or custom classification systems such as Uniclass, Omniclass and more. IFC is
|
||||
able to integrate with any external classification system.
|
||||
|
||||
This API allows you to manage and assign external classification systems and
|
||||
references.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_classification import add_classification
|
||||
from .add_reference import add_reference
|
||||
from .edit_classification import edit_classification
|
||||
from .edit_reference import edit_reference
|
||||
from .remove_classification import remove_classification
|
||||
from .remove_reference import remove_reference
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,72 +17,78 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.date
|
||||
from typing import Union
|
||||
|
||||
|
||||
def add_classification(
|
||||
file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new classification system to the project
|
||||
|
||||
External classification systems such as Uniclass or Omniclass are
|
||||
ways of categorising elements in the AEC industry, typically
|
||||
standardised or nominated by governments or companies. A system
|
||||
typically contains a series of hierarchical reference codes and labels
|
||||
like Pr_12_23_34.
|
||||
|
||||
Classifications may be applied to many things, not just physical
|
||||
elements, such as doors and windows, spatial elements, tasks, cost
|
||||
items, or even resources.
|
||||
|
||||
Prior to assigning classificaion references, you need to add the name
|
||||
and metadata of the classification system that you will use in your
|
||||
project. Classification systems may be revised over time, so this
|
||||
metadata includes the edition date.
|
||||
|
||||
Common classification systems are provided as an IFC library which may
|
||||
be downloaded from https://github.com/Moult/IfcClassification for your
|
||||
convenience. It is advised to use these to ensure that the
|
||||
classification metadata is standardised.
|
||||
|
||||
Adding a classification system will not add the entire hierarchy of
|
||||
references available in the classification. References need to be added
|
||||
separately. Typically, you'd only add the references that you use in
|
||||
your project, see ifcopenshell.api.classification.add_reference for more
|
||||
information.
|
||||
|
||||
:param classification: If a string is provided, it is assumed to be the
|
||||
name of your classification system. This is necessary if you are
|
||||
creating your own custom classification system. Alternatively, you
|
||||
may provide an entity_instance of an IfcClassification from an IFC
|
||||
classification library. The latter approach is preferred if you are
|
||||
using a commonly known system such as Uniclass, as this will ensure
|
||||
all metadata is added correctly.
|
||||
:type classification: str,ifcopenshell.entity_instance
|
||||
:return: The added IfcClassification element
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding a custom clasification from scratch
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification="MyCustomClassification")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
classification = library.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"classification": classification,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]):
|
||||
"""Adds a new classification system to the project
|
||||
|
||||
External classification systems such as Uniclass or Omniclass are
|
||||
ways of categorising elements in the AEC industry, typically
|
||||
standardised or nominated by governments or companies. A system
|
||||
typically contains a series of hierarchical reference codes and labels
|
||||
like Pr_12_23_34.
|
||||
|
||||
Classifications may be applied to many things, not just physical
|
||||
elements, such as doors and windows, spatial elements, tasks, cost
|
||||
items, or even resources.
|
||||
|
||||
Prior to assigning classificaion references, you need to add the name
|
||||
and metadata of the classification system that you will use in your
|
||||
project. Classification systems may be revised over time, so this
|
||||
metadata includes the edition date.
|
||||
|
||||
Common classification systems are provided as an IFC library which may
|
||||
be downloaded from https://github.com/Moult/IfcClassification for your
|
||||
convenience. It is advised to use these to ensure that the
|
||||
classification metadata is standardised.
|
||||
|
||||
Adding a classification system will not add the entire hierarchy of
|
||||
references available in the classification. References need to be added
|
||||
separately. Typically, you'd only add the references that you use in
|
||||
your project, see ifcopenshell.api.classification.add_reference for more
|
||||
information.
|
||||
|
||||
:param classification: If a string is provided, it is assumed to be the
|
||||
name of your classification system. This is necessary if you are
|
||||
creating your own custom classification system. Alternatively, you
|
||||
may provide an entity_instance of an IfcClassification from an IFC
|
||||
classification library. The latter approach is preferred if you are
|
||||
using a commonly known system such as Uniclass, as this will ensure
|
||||
all metadata is added correctly.
|
||||
:type classification: str,ifcopenshell.entity_instance.entity_instance
|
||||
:return: The added IfcClassification element
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding a custom clasification from scratch
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification="MyCustomClassification")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
classification = library.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"classification": classification,
|
||||
}
|
||||
|
||||
def execute(self) -> ifcopenshell.entity_instance:
|
||||
def execute(self):
|
||||
if isinstance(self.settings["classification"], str):
|
||||
classification = self.file.createIfcClassification(Name=self.settings["classification"])
|
||||
self.relate_to_project(classification)
|
||||
|
||||
@@ -18,122 +18,125 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
def add_reference(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
reference: Optional[ifcopenshell.entity_instance] = None,
|
||||
identification: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
classification: Optional[ifcopenshell.entity_instance] = None,
|
||||
is_lightweight=True,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Adds a new classification reference and assigns it to the list of products
|
||||
|
||||
A classification reference is a single entry such as "Pr_12_23_34" that
|
||||
is part of an external classification system (such as Uniclass or
|
||||
Omniclass).
|
||||
|
||||
References can be added to almost any object in IFC, including physical
|
||||
objects, object types, properties, tasks, costs, resources, or even
|
||||
resources such as profiles, documents, libraries, and so on.
|
||||
|
||||
Classification references can be added in two ways. Option 1) specify a
|
||||
custom arbitrary reference, where you have to manually specify the
|
||||
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
|
||||
Option 2) add a reference from an IFC classification library. The latter
|
||||
is preferred if you are using a common classification system such as
|
||||
Uniclass, as the library will be prepopulated with all the valid
|
||||
classifications already.
|
||||
|
||||
Objects are allowed to have multiple classification references from
|
||||
multiple classification systems. This means that adding a new reference
|
||||
will not remove existing references.
|
||||
|
||||
References can be inherited from types. This means that if an
|
||||
IfcWallType has a classification reference of Pr_12_23_34, then all
|
||||
IfcWall occurrences of that type automatically get the same
|
||||
classification of Pr_12_23_34. This means that it is more efficient to
|
||||
assign to types where possible. If a classification reference is
|
||||
assigned to both the type and an occurrence, then the assignment at the
|
||||
occurrence will override the type classification.
|
||||
|
||||
:param product: The list of IFC objects, properties, or resources you want to
|
||||
associate the classification reference to.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param reference: The classification reference entity taken from an
|
||||
IFC classification library. If you supply this parameter, you will
|
||||
use option 2.
|
||||
:type reference: ifcopenshell.entity_instance, optional
|
||||
:param identification: If you choose option 1 and do not specify a
|
||||
reference, you may manually specify an identification code. The code
|
||||
is typically a short identifier and may have punctuation to separate
|
||||
the levels of hierarchy in the classificaion (e.g. Pr_12_23_34).
|
||||
:type identification: str, optional
|
||||
:param name: If you choose option 1 and do not specify a reference, you
|
||||
may manually specify a name. The name is typically human readable.
|
||||
:type name: str, optional
|
||||
:param classification: The IfcClassification entity in your IFC model
|
||||
(not the library, if you are doing option 2) that the reference is
|
||||
part of.
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:param is_lightweight: If you are doing option 2, choose whether or not
|
||||
to only add that particular reference (lighweight) or also add all
|
||||
of its parent references in the classification hierarchy (not
|
||||
lighweight). For example, adding a lightweight reference to
|
||||
Pr_12_23_34 will only add Pr_12_23_34, but adding a heavy reference
|
||||
to Pr_12_23_34 will also add Pr_12_23 and Pr_12. These parent
|
||||
references merely help describe the "tree" of classifications, but
|
||||
is generally unnecessary. Using lightweight classifications are
|
||||
recommended and is the default.
|
||||
:type is_lightweight: bool, optional
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: The newly added IfcClassificationReference
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding and assigning a new reference from scratch
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
lib_classification = library.by_type("IfcClassification")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification=lib_classification)
|
||||
reference = [r for r in library.by_type("IfcClassificationReference")
|
||||
if r.Identification == "XYZ"][0]
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
reference=reference)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"reference": reference,
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"classification": classification,
|
||||
"is_lightweight": is_lightweight,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
reference: Optional[ifcopenshell.entity_instance] = None,
|
||||
identification: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
classification: Optional[ifcopenshell.entity_instance] = None,
|
||||
is_lightweight=True,
|
||||
):
|
||||
"""Adds a new classification reference and assigns it to the list of products
|
||||
|
||||
A classification reference is a single entry such as "Pr_12_23_34" that
|
||||
is part of an external classification system (such as Uniclass or
|
||||
Omniclass).
|
||||
|
||||
References can be added to almost any object in IFC, including physical
|
||||
objects, object types, properties, tasks, costs, resources, or even
|
||||
resources such as profiles, documents, libraries, and so on.
|
||||
|
||||
Classification references can be added in two ways. Option 1) specify a
|
||||
custom arbitrary reference, where you have to manually specify the
|
||||
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
|
||||
Option 2) add a reference from an IFC classification library. The latter
|
||||
is preferred if you are using a common classification system such as
|
||||
Uniclass, as the library will be prepopulated with all the valid
|
||||
classifications already.
|
||||
|
||||
Objects are allowed to have multiple classification references from
|
||||
multiple classification systems. This means that adding a new reference
|
||||
will not remove existing references.
|
||||
|
||||
References can be inherited from types. This means that if an
|
||||
IfcWallType has a classification reference of Pr_12_23_34, then all
|
||||
IfcWall occurrences of that type automatically get the same
|
||||
classification of Pr_12_23_34. This means that it is more efficient to
|
||||
assign to types where possible. If a classification reference is
|
||||
assigned to both the type and an occurrence, then the assignment at the
|
||||
occurrence will override the type classification.
|
||||
|
||||
:param product: The list of IFC objects, properties, or resources you want to
|
||||
associate the classification reference to.
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param reference: The classification reference entity taken from an
|
||||
IFC classification library. If you supply this parameter, you will
|
||||
use option 2.
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance, optional
|
||||
:param identification: If you choose option 1 and do not specify a
|
||||
reference, you may manually specify an identification code. The code
|
||||
is typically a short identifier and may have punctuation to separate
|
||||
the levels of hierarchy in the classificaion (e.g. Pr_12_23_34).
|
||||
:type identification: str, optional
|
||||
:param name: If you choose option 1 and do not specify a reference, you
|
||||
may manually specify a name. The name is typically human readable.
|
||||
:type name: str, optional
|
||||
:param classification: The IfcClassification entity in your IFC model
|
||||
(not the library, if you are doing option 2) that the reference is
|
||||
part of.
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:param is_lightweight: If you are doing option 2, choose whether or not
|
||||
to only add that particular reference (lighweight) or also add all
|
||||
of its parent references in the classification hierarchy (not
|
||||
lighweight). For example, adding a lightweight reference to
|
||||
Pr_12_23_34 will only add Pr_12_23_34, but adding a heavy reference
|
||||
to Pr_12_23_34 will also add Pr_12_23 and Pr_12. These parent
|
||||
references merely help describe the "tree" of classifications, but
|
||||
is generally unnecessary. Using lightweight classifications are
|
||||
recommended and is the default.
|
||||
:type is_lightweight: bool, optional
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: The newly added IfcClassificationReference
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding and assigning a new reference from scratch
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
lib_classification = library.by_type("IfcClassification")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification=lib_classification)
|
||||
reference = [r for r in library.by_type("IfcClassificationReference")
|
||||
if r.Identification == "XYZ"][0]
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
reference=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"reference": reference,
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"classification": classification,
|
||||
"is_lightweight": is_lightweight,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def execute(self):
|
||||
if not self.settings["products"]:
|
||||
return
|
||||
|
||||
|
||||
@@ -15,34 +15,35 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, classification=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassification
|
||||
def edit_classification(
|
||||
file: ifcopenshell.file, classification: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassification
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassification, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassification, consult the IFC documentation.
|
||||
|
||||
:param classification: The IfcClassification entity you want to edit
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param classification: The IfcClassification entity you want to edit
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the classification system to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_classification", model,
|
||||
classification=classification, attributes={"Name": "Foo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"classification": classification, "attributes": attributes or {}}
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the classification system to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_classification", model,
|
||||
classification=classification, attributes={"Name": "Foo"})
|
||||
"""
|
||||
settings = {"classification": classification, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["classification"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["classification"], name, value)
|
||||
|
||||
@@ -15,34 +15,35 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, reference=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file, reference: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassificationReference, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassificationReference, consult the IFC documentation.
|
||||
|
||||
:param reference: The IfcClassificationReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param reference: The IfcClassificationReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
reference = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the reference to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_reference", model,
|
||||
reference=reference, attributes={"Name": "Foo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": reference, "attributes": attributes or {}}
|
||||
reference = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the reference to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_reference", model,
|
||||
reference=reference, attributes={"Name": "Foo"})
|
||||
"""
|
||||
settings = {"reference": reference, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["reference"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["reference"], name, value)
|
||||
|
||||
@@ -20,30 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_classification(file: ifcopenshell.file, classification: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes an IfcClassification from the project and all references
|
||||
|
||||
The classification and all of its relationships, children references,
|
||||
and relationships between objects and child references are completely
|
||||
removed from a project.
|
||||
|
||||
:param classification: The IfcClassification entity you want to remove
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.remove_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"classification": classification}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, classification=None):
|
||||
"""Removes an IfcClassification from the project and all references
|
||||
|
||||
The classification and all of its relationships, children references,
|
||||
and relationships between objectse and child references are completely
|
||||
removed from a project.
|
||||
|
||||
:param classification: The IfcClassification entity you want to remove
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.remove_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"classification": classification}
|
||||
|
||||
def execute(self):
|
||||
references = self.get_references(self.settings["classification"])
|
||||
for reference in references:
|
||||
@@ -55,15 +58,20 @@ class Usecase:
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification):
|
||||
if self.file.schema != "IFC2X3":
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
if not classification.HasReferences:
|
||||
return results
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
if self.file.schema == "IFC2X3":
|
||||
for reference in self.file.by_type("IfcClassificationReference"):
|
||||
if reference.ReferencedSource == classification:
|
||||
results.append(reference)
|
||||
else:
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
return results
|
||||
|
||||
@@ -21,107 +21,102 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
):
|
||||
"""Removes a classification reference from the list of products
|
||||
def remove_reference(
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
) -> None:
|
||||
"""Removes a classification reference from the list of products
|
||||
|
||||
If the classification reference is no longer associated to any products,
|
||||
the classification reference itself is also removed.
|
||||
If the classification reference is no longer associated to any products,
|
||||
the classification reference itself is also removed.
|
||||
|
||||
:param reference: The IfcClassificationReference entity of the
|
||||
relationship you want to remove.
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:param product: The list fo object entities of the relationship you want to
|
||||
remove.
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param reference: The IfcClassificationReference entity of the
|
||||
relationship you want to remove.
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param product: The list fo object entities of the relationship you want to
|
||||
remove.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
reference = ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
ifcopenshell.api.run("classification.remove_reference", model,
|
||||
reference=reference, products=[wall_type])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": reference, "products": products}
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
reference = ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
ifcopenshell.api.run("classification.remove_reference", model,
|
||||
reference=reference, products=[wall_type])
|
||||
"""
|
||||
settings = {"reference": reference, "products": products}
|
||||
|
||||
def execute(self) -> None:
|
||||
is_ifc2x3 = self.file.schema == "IFC2X3"
|
||||
products = set(self.settings["products"])
|
||||
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||
products -= products.difference(referenced)
|
||||
is_ifc2x3 = file.schema == "IFC2X3"
|
||||
products = set(settings["products"])
|
||||
referenced = ifcopenshell.util.element.get_referenced_elements(settings["reference"])
|
||||
products -= products.difference(referenced)
|
||||
|
||||
# all products are already unassigned from a reference
|
||||
if not products:
|
||||
return
|
||||
# all products are already unassigned from a reference
|
||||
if not products:
|
||||
return
|
||||
|
||||
rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
non_rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
for product in self.settings["products"]:
|
||||
if product.is_a("IfcRoot"):
|
||||
rooted_products.add(product)
|
||||
rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
non_rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
for product in settings["products"]:
|
||||
if product.is_a("IfcRoot"):
|
||||
rooted_products.add(product)
|
||||
else:
|
||||
non_rooted_products.add(product)
|
||||
|
||||
if non_rooted_products and is_ifc2x3:
|
||||
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
|
||||
|
||||
if rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in rooted_products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesClassification") and rel.RelatingClassification == settings["reference"]
|
||||
}
|
||||
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
else:
|
||||
non_rooted_products.add(product)
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
if non_rooted_products and is_ifc2x3:
|
||||
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
|
||||
if non_rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in non_rooted_products:
|
||||
rels = getattr(product, "HasExternalReferences", None)
|
||||
if rels is None:
|
||||
rels = getattr(product, "HasExternalReference", [])
|
||||
reference_rels.update(rels)
|
||||
|
||||
if rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in rooted_products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == settings["reference"]}
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedResourceObjects = list(related_objects)
|
||||
else:
|
||||
file.remove(rel)
|
||||
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesClassification")
|
||||
and rel.RelatingClassification == self.settings["reference"]
|
||||
}
|
||||
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
|
||||
if non_rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in non_rooted_products:
|
||||
rels = getattr(product, "HasExternalReferences", None)
|
||||
if rels is None:
|
||||
rels = getattr(product, "HasExternalReference", [])
|
||||
reference_rels.update(rels)
|
||||
|
||||
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == self.settings["reference"]}
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedResourceObjects = list(related_objects)
|
||||
else:
|
||||
self.file.remove(rel)
|
||||
|
||||
# TODO: we only handle lightweight classifications here
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||
if not referenced_elements:
|
||||
self.file.remove(self.settings["reference"])
|
||||
# TODO: we only handle lightweight classifications here
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["reference"])
|
||||
if not referenced_elements:
|
||||
file.remove(settings["reference"])
|
||||
|
||||
@@ -15,3 +15,22 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Constraints are an advanced feature allowing you to specify parametric
|
||||
limits on properties
|
||||
|
||||
Warning: usage of constraints are mostly untested in real life applications.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_metric import add_metric
|
||||
from .add_metric_reference import add_metric_reference
|
||||
from .add_objective import add_objective
|
||||
from .assign_constraint import assign_constraint
|
||||
from .edit_metric import edit_metric
|
||||
from .edit_objective import edit_objective
|
||||
from .remove_constraint import remove_constraint
|
||||
from .remove_metric import remove_metric
|
||||
from .unassign_constraint import unassign_constraint
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -19,44 +19,41 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, objective=None):
|
||||
"""Add a new metric benchmark
|
||||
def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Add a new metric benchmark
|
||||
|
||||
Qualitative constraints may have a series of quantitative benchmarks
|
||||
linked to it known as metrics. Metrics may be parametrically linked to
|
||||
computed model properties or quantities. Metrics need to be satisfied
|
||||
to meet the objective of the constraint.
|
||||
Qualitative constraints may have a series of quantitative benchmarks
|
||||
linked to it known as metrics. Metrics may be parametrically linked to
|
||||
computed model properties or quantities. Metrics need to be satisfied
|
||||
to meet the objective of the constraint.
|
||||
|
||||
:param objective: The IfcObjective that this metric is a benchmark of.
|
||||
:type objective: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcMetric entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:param objective: The IfcObjective that this metric is a benchmark of.
|
||||
:type objective: ifcopenshell.entity_instance
|
||||
:return: The newly created IfcMetric entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"objective": objective,
|
||||
}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
"""
|
||||
settings = {
|
||||
"objective": objective,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
metric = self.file.create_entity(
|
||||
"IfcMetric",
|
||||
**{
|
||||
"Name": "Unnamed",
|
||||
"ConstraintGrade": "NOTDEFINED",
|
||||
"Benchmark": "EQUALTO",
|
||||
}
|
||||
)
|
||||
if self.settings["objective"]:
|
||||
benchmark_values = list(self.settings["objective"].BenchmarkValues or [])
|
||||
benchmark_values.append(metric)
|
||||
self.settings["objective"].BenchmarkValues = benchmark_values
|
||||
return metric
|
||||
metric = file.create_entity(
|
||||
"IfcMetric",
|
||||
**{
|
||||
"Name": "Unnamed",
|
||||
"ConstraintGrade": "NOTDEFINED",
|
||||
"Benchmark": "EQUALTO",
|
||||
},
|
||||
)
|
||||
if settings["objective"]:
|
||||
benchmark_values = list(settings["objective"].BenchmarkValues or [])
|
||||
benchmark_values.append(metric)
|
||||
settings["objective"].BenchmarkValues = benchmark_values
|
||||
return metric
|
||||
|
||||
@@ -18,28 +18,28 @@
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None, reference_path=None):
|
||||
"""
|
||||
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
|
||||
Used to reference a value of an attribute of an instance through a metric objective entity.
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric, "reference_path": reference_path}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["reference_path"]:
|
||||
attributes = self.settings["reference_path"].split(".")
|
||||
references_created = []
|
||||
for i in range(len(attributes)):
|
||||
if i == 0:
|
||||
reference = self.file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
self.settings["metric"].ReferencePath = reference
|
||||
references_created.append(reference)
|
||||
else:
|
||||
reference = self.file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
references_created[i-1].InnerReference = reference
|
||||
references_created.append(reference)
|
||||
return references_created
|
||||
def add_metric_reference(
|
||||
file: ifcopenshell.file, metric: ifcopenshell.entity_instance, reference_path: str
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
|
||||
Used to reference a value of an attribute of an instance through a metric objective entity.
|
||||
"""
|
||||
settings = {"metric": metric, "reference_path": reference_path}
|
||||
|
||||
references_created = []
|
||||
if settings["reference_path"]:
|
||||
attributes = settings["reference_path"].split(".")
|
||||
for i in range(len(attributes)):
|
||||
if i == 0:
|
||||
reference = file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
settings["metric"].ReferencePath = reference
|
||||
references_created.append(reference)
|
||||
else:
|
||||
reference = file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
references_created[i - 1].InnerReference = reference
|
||||
references_created.append(reference)
|
||||
return references_created
|
||||
|
||||
@@ -19,34 +19,31 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file):
|
||||
"""Add a new objective constraint
|
||||
def add_objective(file: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
"""Add a new objective constraint
|
||||
|
||||
Parametric constraints may be defined by the user. The constraint is defined
|
||||
by first creating an objective describing the purpose of the constraint and
|
||||
whether it is a hard or soft constraint. Later on, metrics may be added to
|
||||
check whether the constraint has been met by connecting it to properties and
|
||||
quantities. See ifcopenshell.api.constraint.add_metric for more information.
|
||||
Parametric constraints may be defined by the user. The constraint is defined
|
||||
by first creating an objective describing the purpose of the constraint and
|
||||
whether it is a hard or soft constraint. Later on, metrics may be added to
|
||||
check whether the constraint has been met by connecting it to properties and
|
||||
quantities. See ifcopenshell.api.constraint.add_metric for more information.
|
||||
|
||||
:return: The newly created IfcObjective entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcObjective entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# Create a new objective for code compliance requirements
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
objective.ConstraintGrade = "ADVISORY"
|
||||
objective.ObjectiveQualifier = "CODECOMPLIANCE"
|
||||
# Note: the objective right now is purely qualitative and for
|
||||
# information purposes. You may wish to add quantiative metrics.
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {}
|
||||
# Create a new objective for code compliance requirements
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
objective.ConstraintGrade = "ADVISORY"
|
||||
objective.ObjectiveQualifier = "CODECOMPLIANCE"
|
||||
# Note: the objective right now is purely qualitative and for
|
||||
# information purposes. You may wish to add quantiative metrics.
|
||||
"""
|
||||
settings = {}
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(
|
||||
"IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"}
|
||||
)
|
||||
return file.create_entity(
|
||||
"IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"}
|
||||
)
|
||||
|
||||
@@ -18,42 +18,45 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Union
|
||||
|
||||
|
||||
def assign_constraint(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a constraint to a list of products
|
||||
|
||||
This assigns a relationship between a product and a constraint, so that
|
||||
when a product's properties and quantities do not match the requirements
|
||||
of the constraint's metrics, results can be flagged.
|
||||
|
||||
It is assumed (but not explicit in the IFC documentation) that
|
||||
constraints are inherited from the type. This way, it is not necessary
|
||||
to create lots of constraint assignments.
|
||||
|
||||
:param products: The list of products the constraint applies to. This is anything
|
||||
which can have properties or quantities.
|
||||
:type products: list[ifcopenshell.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: The new or updated IfcRelAssociatesConstraint relationship
|
||||
or `None` if `products` was an empty list.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns a constraint to a list of products
|
||||
|
||||
This assigns a relationship between a product and a constraint, so that
|
||||
when a product's properties and quantities do not match the requirements
|
||||
of the constraint's metrics, results can be flagged.
|
||||
|
||||
It is assumed (but not explicit in the IFC documentation) that
|
||||
constraints are inherited from the type. This way, it is not necessary
|
||||
to create lots of constraint assignments.
|
||||
|
||||
:param products: The list of products the constraint applies to. This is anything
|
||||
which can have properties or quantities.
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The new or updated IfcRelAssociatesConstraint relationship
|
||||
or `None` if `products` was an empty list.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def execute(self):
|
||||
products = set(self.settings["products"])
|
||||
if not products:
|
||||
return
|
||||
|
||||
@@ -15,35 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None, attributes=None):
|
||||
"""Edit the attributes of a metric
|
||||
def edit_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a metric
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcMetric, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcMetric, consult the IFC documentation.
|
||||
|
||||
:param metric: The IfcMetric you want to edit.
|
||||
:type metric: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param metric: The IfcMetric you want to edit.
|
||||
:type metric: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.edit_metric", model,
|
||||
metric=metric, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric, "attributes": attributes or {}}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.edit_metric", model,
|
||||
metric=metric, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
settings = {"metric": metric, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["metric"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["metric"], name, value)
|
||||
|
||||
@@ -15,33 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, objective=None, attributes=None):
|
||||
"""Edit the attributes of a objective
|
||||
def edit_objective(
|
||||
file: ifcopenshell.file, objective: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edit the attributes of a objective
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcObjective, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcObjective, consult the IFC documentation.
|
||||
|
||||
:param objective: The IfcObjective you want to edit.
|
||||
:type objective: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param objective: The IfcObjective you want to edit.
|
||||
:type objective: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.edit_objective", model,
|
||||
objective=objective, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"objective": objective, "attributes": attributes or {}}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.edit_objective", model,
|
||||
objective=objective, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
settings = {"objective": objective, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["objective"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["objective"], name, value)
|
||||
|
||||
@@ -20,36 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, constraint=None):
|
||||
"""Remove a constraint (typically an objective)
|
||||
def remove_constraint(file: ifcopenshell.file, constraint: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a constraint (typically an objective)
|
||||
|
||||
Removes a constraint definition and all of its associations to any
|
||||
products. Typically this would be an IfcObjective, although technically
|
||||
you can associate IfcMetrics ith products too, though the meaning may be
|
||||
unclear.
|
||||
Removes a constraint definition and all of its associations to any
|
||||
products. Typically this would be an IfcObjective, although technically
|
||||
you can associate IfcMetrics ith products too, though the meaning may be
|
||||
unclear.
|
||||
|
||||
:param constraint: The IfcObjective you want to remove.
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param constraint: The IfcObjective you want to remove.
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.remove_constraint", model,
|
||||
constraint=objective)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"constraint": constraint}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.remove_constraint", model,
|
||||
constraint=objective)
|
||||
"""
|
||||
settings = {"constraint": constraint}
|
||||
|
||||
def execute(self):
|
||||
self.file.remove(self.settings["constraint"])
|
||||
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
|
||||
if not rel.RelatingConstraint:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
file.remove(settings["constraint"])
|
||||
for rel in file.by_type("IfcRelAssociatesConstraint"):
|
||||
if not rel.RelatingConstraint:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,33 +15,37 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a metric benchmark
|
||||
|
||||
Removes a metric benchmark and all of its associations to any products
|
||||
and objectives.
|
||||
|
||||
:param metric: The IfcMetric you want to remove.
|
||||
:type metric: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.remove_metric", model,
|
||||
metric=metric)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"metric": metric}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None):
|
||||
"""Remove a metric benchmark
|
||||
|
||||
Removes a metric benchmark and all of its associations to any products
|
||||
and objectives.
|
||||
|
||||
:param metric: The IfcMetric you want to remove.
|
||||
:type metric: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.remove_metric", model,
|
||||
metric=metric)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["metric"].ReferencePath:
|
||||
reference = self.settings["metric"].ReferencePath
|
||||
|
||||
@@ -21,31 +21,33 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_constraint(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a constraint from a list of products
|
||||
|
||||
The constraint will not be deleted and is available to be assigned to
|
||||
other products.
|
||||
|
||||
:param products: The list of products the constraint applies to.
|
||||
:type products: list[ifcopenshell.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Unassigns a constraint from a list of products
|
||||
|
||||
The constraint will not be deleted and is available to be assigned to
|
||||
other products.
|
||||
|
||||
:param products: The list of products the constraint applies to.
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
products = set(self.settings["products"])
|
||||
if not products:
|
||||
|
||||
@@ -15,3 +15,19 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Contexts allow you to classify when geometry should be used in different
|
||||
purposes
|
||||
|
||||
For example, a door may have many geometries assigned to it: a 3D body
|
||||
geometry, a clearance zone for disabled access and egress, and a 2D top down
|
||||
plan view representation annotating swing extents. Each geometry is assigned to
|
||||
a context to distinguish its purpose and level of detail.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_context import add_context
|
||||
from .edit_context import edit_context
|
||||
from .remove_context import remove_context
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -17,168 +17,171 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
def add_context(file, context_type=None, context_identifier=None, target_view=None, parent=None) -> None:
|
||||
"""Adds a new geometric representation context
|
||||
|
||||
In IFC, physical objects may have zero, one, or multiple geometric
|
||||
representations associated with it. For example, a building storey might
|
||||
not have any geometry, but simply be a coordinate in space.
|
||||
Alternatively, a wall might have a 3D body representation in the form of
|
||||
a cuboid. As a final example, a door might also have a 3D body
|
||||
representation of a 3D door panel and door frame, but may additionally
|
||||
have a 2D door plan view representation of the door swing, and even a 2D
|
||||
elevation view of the door, a 3D box representing the disabled clearance
|
||||
zone of the door, a 2D profile representing the profile of the door to
|
||||
cut out in a wall, and so on. In this situation, a door will have
|
||||
multiple geometric representations.
|
||||
|
||||
To distinguish between the different purposes of multiple geometric
|
||||
representations, each geometric representation must belong to a
|
||||
geometric representation "context". There are typically always 2
|
||||
contexts, one for 3D representations and one for 2D representations.
|
||||
These 2 contexts then have subcontexts for things like the 3D body
|
||||
representation, clearance representations, annotation representations,
|
||||
and so on. Each representation of a physical IFC product (e.g. a door)
|
||||
must be assigned to one of these subcontexts. Therefore setting up
|
||||
appropriate contexts is critical prior to authoring any IFC model which
|
||||
contains geometry.
|
||||
|
||||
There are two steps to setting up appropriate subcontexts. First, a 2D
|
||||
and/or 3D context must be added. These must be always called the "Model"
|
||||
context for 3D and the "Plan" context for 2D (even if the 2D geometry is
|
||||
not a plan view). Then, one or more subcontexts are added using either
|
||||
the "Model" or "Plan" as their parent. These subcontexts are further
|
||||
distinguished using an "identifier" and "target view". The "identifier"
|
||||
describes the purpose of the representation, and the "target view"
|
||||
describes the typical diagrammatic presentation that context's geometry
|
||||
should be viewed in. The most common identifiers you might use are:
|
||||
|
||||
- Body: for the actual shape of the object
|
||||
- Box: the bounding box of the object (useful for shape analytics)
|
||||
- Axis: the parametric line determining the shape of the object
|
||||
- Profile: the elevation silhouette of the object, useful for cutting
|
||||
out holes for the object to fit into host elements
|
||||
- Footprint: the plan view silhouette of the object, useful for certain
|
||||
quantity take-off rules
|
||||
- Clearance: the clearance zone of the object
|
||||
- Annotation: symbolic annotations typically used in diagrams or
|
||||
drawings
|
||||
|
||||
The most common "target views" you might use are:
|
||||
|
||||
- MODEL_VIEW: for 3D geometry you might see in a BIM viewer
|
||||
- PLAN_VIEW: for 2D geometry you might see in a plan representation
|
||||
- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation
|
||||
- SECTION_VIEW: for 2D geometry you might see in a section representation
|
||||
- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams
|
||||
you might use for structural frame analysis, axis-based parametric
|
||||
modeling
|
||||
- SKETCH_VIEW: for viewing abstract high-level representations such as
|
||||
in bubble diagrams of spatial topology
|
||||
|
||||
This may sound like a lot, but after a few typical contexts are set up
|
||||
at the beginning, it becomes easy to navigate and isolate geometry for
|
||||
different purposes. There is also the concept of a target scale, which
|
||||
represents the zoom level detail of geometry, but this is not currently
|
||||
supported by this API. Setting up all these contexts are also optional,
|
||||
and you may only use a single Model context and Body subcontext for
|
||||
simple models, but this simplification sacrifices the ability of more
|
||||
parametric or analytical usecases.
|
||||
|
||||
:param context_type: The type of the context, must be one of "Model" or
|
||||
"Plan" only.
|
||||
:type context_type: str
|
||||
:param context_identifier: The identifier of the context, chosen from
|
||||
one of the common identifiers above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type context_identifier: str, optional
|
||||
:param target_view: the target view of the context, chosen from one of
|
||||
the common target views above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type target_view: str, optional
|
||||
:param parent: the parent context. Must be left as None (the default)
|
||||
for contexts, and only set for subcontexts. Note that there are only
|
||||
contexts and subcontexts, a subcontext cannot have any children.
|
||||
:type parent: ifcopenshell.entity_instance, optional
|
||||
:return: the newly created IfcGeometricRepresentationContext or
|
||||
IfcGeometricRepresentationSubContext entity
|
||||
:rtype: ifcopenshell.entity_instance, optional
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# If we plan to store 3D geometry in our IFC model, we have to setup
|
||||
# a "Model" context.
|
||||
model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
|
||||
# And/Or, if we plan to store 2D geometry, we need a "Plan" context
|
||||
plan = ifcopenshell.api.run("context.add_context", model, context_type="Plan")
|
||||
|
||||
# Now we setup the subcontexts with each of the geometric "purposes"
|
||||
# we plan to store in our model. "Body" is by far the most important
|
||||
# and common context, as most IFC models are assumed to be viewable
|
||||
# in 3D.
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Axis subcontext is important if any "axis-based" parametric
|
||||
# geometry is going to be created. For example, a beam, or column
|
||||
# may be drawn using a single 3D axis line, and for this we need an
|
||||
# Axis subcontext.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Box subcontext is useful for clash detection or shape
|
||||
# analysis, or even lazy-loading of large models.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# It's also important to have a 2D Axis subcontext for things like
|
||||
# walls and claddings which can be drawn using a 2D axis line.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
|
||||
|
||||
# A 2D annotation subcontext for plan views are important for door
|
||||
# swings, window cuts, and symbols for equipment like GPOs, fire
|
||||
# extinguishers, and so on.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
|
||||
|
||||
# You may also create 2D annotation subcontexts for sections and
|
||||
# elevation views.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
|
||||
|
||||
# Let's create a new wall. The wall does not have any geometry yet.
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
|
||||
# Let's use the "3D Body" representation we created earlier to add a
|
||||
# new wall-like body geometry, 5 meters long, 3 meters high, and
|
||||
# 200mm thick
|
||||
representation = ifcopenshell.api.run("geometry.add_wall_representation", model,
|
||||
context=body, length=5, height=3, thickness=0.2)
|
||||
|
||||
# Assign our new body geometry back to our wall
|
||||
ifcopenshell.api.run("geometry.assign_representation", model,
|
||||
product=wall, representation=representation)
|
||||
|
||||
# Place our wall at the origin
|
||||
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context_type": context_type,
|
||||
"parent": parent,
|
||||
"context_identifier": context_identifier,
|
||||
"target_view": target_view,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context_type=None, context_identifier=None, target_view=None, parent=None):
|
||||
"""Adds a new geometric representation context
|
||||
|
||||
In IFC, physical objects may have zero, one, or multiple geometric
|
||||
representations associated with it. For example, a building storey might
|
||||
not have any geometry, but simply be a coordinate in space.
|
||||
Alternatively, a wall might have a 3D body representation in the form of
|
||||
a cuboid. As a final example, a door might also have a 3D body
|
||||
representation of a 3D door panel and door frame, but may additionally
|
||||
have a 2D door plan view representation of the door swing, and even a 2D
|
||||
elevation view of the door, a 3D box representing the disabled clearance
|
||||
zone of the door, a 2D profile representing the profile of the door to
|
||||
cut out in a wall, and so on. In this situation, a door will have
|
||||
multiple geometric representations.
|
||||
|
||||
To distinguish between the different purposes of multiple geometric
|
||||
representations, each geometric representation must belong to a
|
||||
geometric representation "context". There are typically always 2
|
||||
contexts, one for 3D representations and one for 2D representations.
|
||||
These 2 contexts then have subcontexts for things like the 3D body
|
||||
representation, clearance representations, annotation representations,
|
||||
and so on. Each representation of a physical IFC product (e.g. a door)
|
||||
must be assigned to one of these subcontexts. Therefore setting up
|
||||
appropriate contexts is critical prior to authoring any IFC model which
|
||||
contains geometry.
|
||||
|
||||
There are two steps to setting up appropriate subcontexts. First, a 2D
|
||||
and/or 3D context must be added. These must be always called the "Model"
|
||||
context for 3D and the "Plan" context for 2D (even if the 2D geometry is
|
||||
not a plan view). Then, one or more subcontexts are added using either
|
||||
the "Model" or "Plan" as their parent. These subcontexts are further
|
||||
distinguished using an "identifier" and "target view". The "identifier"
|
||||
describes the purpose of the representation, and the "target view"
|
||||
describes the typical diagrammatic presentation that context's geometry
|
||||
should be viewed in. The most common identifiers you might use are:
|
||||
|
||||
- Body: for the actual shape of the object
|
||||
- Box: the bounding box of the object (useful for shape analytics)
|
||||
- Axis: the parametric line determining the shape of the object
|
||||
- Profile: the elevation silhouette of the object, useful for cutting
|
||||
out holes for the object to fit into host elements
|
||||
- Footprint: the plan view silhouette of the object, useful for certain
|
||||
quantity take-off rules
|
||||
- Clearance: the clearance zone of the object
|
||||
- Annotation: symbolic annotations typically used in diagrams or
|
||||
drawings
|
||||
|
||||
The most common "target views" you might use are:
|
||||
|
||||
- MODEL_VIEW: for 3D geometry you might see in a BIM viewer
|
||||
- PLAN_VIEW: for 2D geometry you might see in a plan representation
|
||||
- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation
|
||||
- SECTION_VIEW: for 2D geometry you might see in a section representation
|
||||
- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams
|
||||
you might use for structural frame analysis, axis-based parametric
|
||||
modeling
|
||||
- SKETCH_VIEW: for viewing abstract high-level representations such as
|
||||
in bubble diagrams of spatial topology
|
||||
|
||||
This may sound like a lot, but after a few typical contexts are set up
|
||||
at the beginning, it becomes easy to navigate and isolate geometry for
|
||||
different purposes. There is also the concept of a target scale, which
|
||||
represents the zoom level detail of geometry, but this is not currently
|
||||
supported by this API. Setting up all these contexts are also optional,
|
||||
and you may only use a single Model context and Body subcontext for
|
||||
simple models, but this simplification sacrifices the ability of more
|
||||
parametric or analytical usecases.
|
||||
|
||||
:param context_type: The type of the context, must be one of "Model" or
|
||||
"Plan" only.
|
||||
:type context_type: str
|
||||
:param context_identifier: The identifier of the context, chosen from
|
||||
one of the common identifiers above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type context_identifier: str, optional
|
||||
:param target_view: the target view of the context, chosen from one of
|
||||
the common target views above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type target_view: str, optional
|
||||
:param parent: the parent context. Must be left as None (the default)
|
||||
for contexts, and only set for subcontexts. Note that there are only
|
||||
contexts and subcontexts, a subcontext cannot have any children.
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance, optional
|
||||
:return: the newly created IfcGeometricRepresentationContext or
|
||||
IfcGeometricRepresentationSubContext entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, optional
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# If we plan to store 3D geometry in our IFC model, we have to setup
|
||||
# a "Model" context.
|
||||
model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
|
||||
# And/Or, if we plan to store 2D geometry, we need a "Plan" context
|
||||
plan = ifcopenshell.api.run("context.add_context", model, context_type="Plan")
|
||||
|
||||
# Now we setup the subcontexts with each of the geometric "purposes"
|
||||
# we plan to store in our model. "Body" is by far the most important
|
||||
# and common context, as most IFC models are assumed to be viewable
|
||||
# in 3D.
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Axis subcontext is important if any "axis-based" parametric
|
||||
# geometry is going to be created. For example, a beam, or column
|
||||
# may be drawn using a single 3D axis line, and for this we need an
|
||||
# Axis subcontext.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Box subcontext is useful for clash detection or shape
|
||||
# analysis, or even lazy-loading of large models.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# It's also important to have a 2D Axis subcontext for things like
|
||||
# walls and claddings which can be drawn using a 2D axis line.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
|
||||
|
||||
# A 2D annotation subcontext for plan views are important for door
|
||||
# swings, window cuts, and symbols for equipment like GPOs, fire
|
||||
# extinguishers, and so on.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
|
||||
|
||||
# You may also create 2D annotation subcontexts for sections and
|
||||
# elevation views.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
|
||||
|
||||
# Let's create a new wall. The wall does not have any geometry yet.
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
|
||||
# Let's use the "3D Body" representation we created earlier to add a
|
||||
# new wall-like body geometry, 5 meters long, 3 meters high, and
|
||||
# 200mm thick
|
||||
representation = ifcopenshell.api.run("geometry.add_wall_representation", model,
|
||||
context=body, length=5, height=3, thickness=0.2)
|
||||
|
||||
# Assign our new body geometry back to our wall
|
||||
ifcopenshell.api.run("geometry.assign_representation", model,
|
||||
product=wall, representation=representation)
|
||||
|
||||
# Place our wall at the origin
|
||||
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context_type": context_type,
|
||||
"parent": parent,
|
||||
"context_identifier": context_identifier,
|
||||
"target_view": target_view,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["parent"]:
|
||||
if self.settings["context_type"] == "Plan":
|
||||
|
||||
@@ -17,37 +17,34 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context, attributes):
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
def edit_context(file, context, attributes) -> None:
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcGeometricRepresentationContext, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcGeometricRepresentationContext, consult the IFC documentation.
|
||||
|
||||
:param context: The IfcGeometricRepresentationContext entity you want to edit
|
||||
:type context: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param context: The IfcGeometricRepresentationContext entity you want to edit
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
|
||||
# Let's fix it!
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": context, "attributes": attributes or {}}
|
||||
# Let's fix it!
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
settings = {"context": context, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["context"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["context"], name, value)
|
||||
|
||||
@@ -17,51 +17,50 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context=None):
|
||||
"""Removes an IfcGeometricRepresentationContext
|
||||
def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes an IfcGeometricRepresentationContext
|
||||
|
||||
Any representation geometry that is assigned to the context is also
|
||||
removed. If a context is removed, then any subcontexts are also removed.
|
||||
Any representation geometry that is assigned to the context is also
|
||||
removed. If a context is removed, then any subcontexts are also removed.
|
||||
|
||||
:param context: The IfcGeometricRepresentationContext entity to remove
|
||||
:type context: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param context: The IfcGeometricRepresentationContext entity to remove
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
|
||||
# Let's just get rid of it completely
|
||||
ifcopenshell.api.run("context.remove_context", model, context=body)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": context}
|
||||
# Let's just get rid of it completely
|
||||
ifcopenshell.api.run("context.remove_context", model, context=body)
|
||||
"""
|
||||
settings = {"context": context}
|
||||
|
||||
def execute(self):
|
||||
for subcontext in self.settings["context"].HasSubContexts:
|
||||
ifcopenshell.api.run("context.remove_context", self.file, context=subcontext)
|
||||
for subcontext in settings["context"].HasSubContexts:
|
||||
ifcopenshell.api.run("context.remove_context", file, context=subcontext)
|
||||
|
||||
if getattr(self.settings["context"], "ParentContext", None):
|
||||
new = self.settings["context"].ParentContext
|
||||
for inverse in self.file.get_inverse(self.settings["context"]):
|
||||
if inverse.is_a("IfcCoordinateOperation"):
|
||||
inverse.SourceCRS = inverse.TargetCRS
|
||||
ifcopenshell.util.element.remove_deep(self.file, inverse)
|
||||
else:
|
||||
ifcopenshell.util.element.replace_attribute(inverse, self.settings["context"], new)
|
||||
self.file.remove(self.settings["context"])
|
||||
else:
|
||||
representations_in_context = self.settings["context"].RepresentationsInContext
|
||||
self.file.remove(self.settings["context"])
|
||||
for element in representations_in_context:
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, representation=element)
|
||||
if getattr(settings["context"], "ParentContext", None):
|
||||
new = settings["context"].ParentContext
|
||||
for inverse in file.get_inverse(settings["context"]):
|
||||
if inverse.is_a("IfcCoordinateOperation"):
|
||||
inverse.SourceCRS = inverse.TargetCRS
|
||||
ifcopenshell.util.element.remove_deep(file, inverse)
|
||||
else:
|
||||
ifcopenshell.util.element.replace_attribute(inverse, settings["context"], new)
|
||||
file.remove(settings["context"])
|
||||
else:
|
||||
representations_in_context = settings["context"].RepresentationsInContext
|
||||
file.remove(settings["context"])
|
||||
for element in representations_in_context:
|
||||
ifcopenshell.api.run("geometry.remove_representation", file, representation=element)
|
||||
|
||||
@@ -15,3 +15,15 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Processes and costs may be controlled by other entities which indicate
|
||||
constraints that determine how they can change
|
||||
|
||||
This is an advanced feature mostly used in 4D/5D
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_control import assign_control
|
||||
from .unassign_control import unassign_control
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
@@ -18,89 +18,89 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_control=None, related_object=None):
|
||||
"""Assigns a planning control or constraint to an object
|
||||
def assign_control(
|
||||
file: ifcopenshell.file,
|
||||
relating_control: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a planning control or constraint to an object
|
||||
|
||||
IFC can describe concepts that control other objects. For example, a
|
||||
planning calendar controls the availability of working days for
|
||||
construction planning. As another example, a cost item might constrain
|
||||
or limit the ability to procure and build a product.
|
||||
IFC can describe concepts that control other objects. For example, a
|
||||
planning calendar controls the availability of working days for
|
||||
construction planning. As another example, a cost item might constrain
|
||||
or limit the ability to procure and build a product.
|
||||
|
||||
This usecase lets you assign controls following the rules of the IFC
|
||||
specification. This is an advanced topic and assumes knowledge of the
|
||||
IFC concepts to determine what is allowed to control what. In the
|
||||
future, this API will likely be deprecated in favour of multiple usecase
|
||||
specific APIs.
|
||||
This usecase lets you assign controls following the rules of the IFC
|
||||
specification. This is an advanced topic and assumes knowledge of the
|
||||
IFC concepts to determine what is allowed to control what. In the
|
||||
future, this API will likely be deprecated in favour of multiple usecase
|
||||
specific APIs.
|
||||
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcRelAssignsToControl. If relationship already
|
||||
existed before and wasn't changed then returns None.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, None
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: The newly created IfcRelAssignsToControl. If relationship already
|
||||
existed before and wasn't changed then returns None.
|
||||
:rtype: ifcopenshell.entity_instance, None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# One common usecase is to assign a calendar to a task
|
||||
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model)
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model)
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=schedule)
|
||||
# One common usecase is to assign a calendar to a task
|
||||
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model)
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model)
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=schedule)
|
||||
|
||||
# All subtasks will inherit this calendar, so assigning a single
|
||||
# calendar to the root task effectively defines a "default" calendar
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=calendar, related_object=task)
|
||||
# All subtasks will inherit this calendar, so assigning a single
|
||||
# calendar to the root task effectively defines a "default" calendar
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=calendar, related_object=task)
|
||||
|
||||
# Another common example might be relating a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
# Another common example might be relating a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for assignment in self.settings["related_object"].HasAssignments:
|
||||
if (
|
||||
assignment.is_a("IfcRelAssignsToControl")
|
||||
and assignment.RelatingControl == self.settings["relating_control"]
|
||||
):
|
||||
return
|
||||
|
||||
controls = None
|
||||
if self.settings["relating_control"].Controls:
|
||||
controls = self.settings["relating_control"].Controls[0]
|
||||
|
||||
if controls:
|
||||
if self.settings["related_object"] in controls.RelatedObjects:
|
||||
if settings["related_object"].HasAssignments:
|
||||
for assignment in settings["related_object"].HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl == settings["relating_control"]:
|
||||
return
|
||||
related_objects = set(controls.RelatedObjects)
|
||||
related_objects.add(self.settings["related_object"])
|
||||
controls.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls})
|
||||
else:
|
||||
controls = self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingControl": self.settings["relating_control"],
|
||||
},
|
||||
)
|
||||
return controls
|
||||
|
||||
controls = None
|
||||
if settings["relating_control"].Controls:
|
||||
controls = settings["relating_control"].Controls[0]
|
||||
|
||||
if controls:
|
||||
if settings["related_object"] in controls.RelatedObjects:
|
||||
return
|
||||
related_objects = set(controls.RelatedObjects)
|
||||
related_objects.add(settings["related_object"])
|
||||
controls.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": controls})
|
||||
else:
|
||||
controls = file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": [settings["related_object"]],
|
||||
"RelatingControl": settings["relating_control"],
|
||||
},
|
||||
)
|
||||
return controls
|
||||
|
||||
@@ -19,56 +19,58 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_control=None, related_object=None):
|
||||
"""Unassigns a planning control or constraint to an object
|
||||
def unassign_control(
|
||||
file: ifcopenshell.file,
|
||||
relating_control: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Unassigns a planning control or constraint to an object
|
||||
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: If the control still is related to other objects, the
|
||||
IfcRelAssignsToControl is returned, otherwise None.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, None
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: If the control still is related to other objects, the
|
||||
IfcRelAssignsToControl is returned, otherwise None.
|
||||
:rtype: ifcopenshell.entity_instance, None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# Let's relate a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
# Let's relate a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
|
||||
# And now let's change our mind
|
||||
ifcopenshell.api.run("control.unassign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
# And now let's change our mind
|
||||
ifcopenshell.api.run("control.unassign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != self.settings["relating_control"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
for rel in settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != settings["relating_control"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
return rel
|
||||
|
||||
@@ -15,3 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manage cost schedules, cost items, cost estimation and parametric quantity
|
||||
take-off
|
||||
|
||||
IFC supports storing cost schedules and detailed cost breakdown structures,
|
||||
including formulas, subtotals, and parametric links to model element
|
||||
quantities.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_cost_item import add_cost_item
|
||||
from .add_cost_item_quantity import add_cost_item_quantity
|
||||
from .add_cost_schedule import add_cost_schedule
|
||||
from .add_cost_value import add_cost_value
|
||||
from .assign_cost_item_quantity import assign_cost_item_quantity
|
||||
from .assign_cost_value import assign_cost_value
|
||||
from .calculate_cost_item_resource_value import calculate_cost_item_resource_value
|
||||
from .copy_cost_item import copy_cost_item
|
||||
from .copy_cost_item_values import copy_cost_item_values
|
||||
from .edit_cost_item import edit_cost_item
|
||||
from .edit_cost_item_quantity import edit_cost_item_quantity
|
||||
from .edit_cost_schedule import edit_cost_schedule
|
||||
from .edit_cost_value import edit_cost_value
|
||||
from .edit_cost_value_formula import edit_cost_value_formula
|
||||
from .remove_cost_item import remove_cost_item
|
||||
from .remove_cost_item_quantity import remove_cost_item_quantity
|
||||
from .remove_cost_schedule import remove_cost_schedule
|
||||
from .remove_cost_value import remove_cost_value
|
||||
from .unassign_cost_item_quantity import unassign_cost_item_quantity
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user