Compare commits

...

84 Commits

Author SHA1 Message Date
Ryan Schultz 0f835ca739 removed unnecessary code 2024-05-28 18:43:13 -05:00
Ryan Schultz 87442f549f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:23 -05:00
Ryan Schultz bf27e41275 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:09 -05:00
Ryan Schultz 66cf06c990 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:10:20 -05:00
Ryan Schultz 5757a62b6f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:28 -05:00
Ryan Schultz e2552e3d46 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:22 -05:00
Ryan Schultz 1b91540c96 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:15 -05:00
Ryan Schultz 274b12e5a0 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:01 -05:00
Ryan Schultz 496ba6f22d fix #3802 - Copy array attributes from another array 2024-05-26 13:56:52 -05:00
Dion Moult b7737cb6d3 Fix #4655. profiles is now available as a keyword in the selector syntax. 2024-05-11 22:57:16 +10:00
Dion Moult 55026ca996 Fix #4616. You can now create a new representation based off any other object. This makes it easy to custom model whatever you want as a starting point. 2024-05-11 22:29:39 +10:00
Dion Moult 3fa573e3c1 Fix #4615. Report to user if they are attempting to create an incompatible context-representation combination. 2024-05-11 20:48:38 +10:00
Dion Moult 00ae6809a9 See #4657. Fallback to copying cache if moving cache fails due to permission error. 2024-05-11 19:59:39 +10:00
Dion Moult c3fbb11093 See #4657. Restrict annotation psets to their particular predefined type. 2024-05-11 19:38:22 +10:00
Kurt Battisti a0d27c3f1c Make Whole status UI message clearer (#4658)
Replace UI text "No Aggregate" with "No Whole relation defined" to avoid misinterpretation when instance is a whole with parts.
2024-05-11 19:04:35 +10:00
Dion Moult 811a908cc3 More segfault fixes in test suite (maybe order matters only on Linux?) 2024-05-11 19:01:57 +10:00
Dion Moult 94e5baf48a Fix #4647. Better error reporting if the user tries to activate a drawing that isn't available. 2024-05-11 19:01:21 +10:00
Dion Moult cd2570cdbb Fix #4649. Bug where editing property set template enumerations didn't clean up properly. 2024-05-11 15:18:18 +10:00
Dion Moult 6df6553935 Notify user if duration parsing not available in util.date 2024-05-11 15:14:39 +10:00
Dion Moult 8e6c372a4f Fix ability to run tests and segfaulting test 2024-05-11 15:00:30 +10:00
Andrej730 371e11de9b root.remove_product to consider IfcRelConnectsPortToElement 2024-05-10 19:18:09 +05:00
Andrej730 d2a708fcc4 fix bug in ifc2x3 tests bootstrap
it was creating new applications and users every time `ifcopenshell.api.owner.settings.get_user` or `ifcopenshell.api.owner.settings.get_application` was called
2024-05-10 19:18:09 +05:00
Andrej730 845d772a08 move sequence.get_related_products to util module 2024-05-10 19:18:09 +05:00
Andrej730 1e630ede08 ifc2x3 tests 2024-05-10 19:18:09 +05:00
Andrej730 c50d1ea2fe typing 2024-05-10 19:18:08 +05:00
Dion Moult e7eb00eaa3 See #4652. Fix bug where auto-detection of a false origin could be incorrect
We were incorrectly multiplying a shape matrix in SI units with a vertex in project units. We also didn't do a final check whether or not that final resultant coordinate was far away or not (for example, origin and vertex can cancel each other out)
2024-05-10 23:28:37 +10:00
Dion Moult cd6eac3c8f See #4652. Fix bug where using create_mesh won't work if you pass it a triangulation. 2024-05-10 22:39:56 +10:00
Dion Moult 69e21da60c Minor naming changes in IfcFM to be more true to IFC jargon 2024-05-10 22:13:39 +10:00
Dion Moult 57b905862a Fix #4650. When the bbim build hotfixes the bot build, it should replace, not copy over. 2024-05-10 18:20:41 +10:00
Dion Moult 51d3a2b77b Whoops 2024-05-10 16:16:43 +10:00
Andrej730 b2920996be remove deprecated api method type.get_related_objects 2024-05-09 18:15:49 +05:00
Andrej730 bdc5a7a01b more descriptive errors creating entities - setting incorrect attributes
example:

```
ifc_file = ifcopenshell.file(schema="IFC4")

entity = ifc_file.create_entity("IfcRoot", "xxx", None, None, None, None)
entity = ifc_file.create_entity("IfcRoot", Location=None)
```
2024-05-09 18:15:49 +05:00
Andrej730 1c6a9e2c49 more descriptive errors settings incorrect data type to attributes
Example:
```
ifc_file = ifcopenshell.file(schema="IFC2X3")
entity = ifc_file.create_entity("IfcCostSchedule")

entity.UpdateDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime")
```
2024-05-09 18:15:49 +05:00
Andrej730 8ffb77551f more tests for ifc2x3 2024-05-09 18:15:49 +05:00
Andrej730 2d35869c41 typing 2024-05-09 18:00:17 +05:00
Andrej730 359dab573b document.remove_information to support ifc2x3 #4636 2024-05-09 14:02:10 +05:00
Andrej730 6e2edbf101 explicitly specify available imported symbols
Fix LSP warnings like ""entity_instance" is not exported from module "ifcopenshell"PylancereportPrivateImportUsage" for `ifcopenshell.file`, `ifcopenshell.entity_instance` etc.

After adding py.typed in 722201a1a it's now a typed library and all imported symbols in modules are considered private by default and should be added to `__all__` if they are supposed to be generally available from imported module.

See https://github.com/microsoft/pyright/blob/main/docs/typed-libraries.md#library-interface
2024-05-09 13:35:29 +05:00
Dion Moult 4c8f93d737 Add error catching to model loading and let users hide error dialog 2024-05-09 16:33:05 +10:00
Dion Moult 2789bc565a Write troubleshooting documentation 2024-05-09 16:21:24 +10:00
Dion Moult a1e0b8858e Track last actions and friendlier error reporting when something breaks 2024-05-09 13:37:57 +10:00
Dion Moult f075a6178b Experimental code for serialising API settings 2024-05-09 13:29:26 +10:00
Dion Moult 5fa385e1e0 Allow for having wildcard listeners in the api 2024-05-09 13:27:08 +10:00
Dion Moult b57141a80c New type of IfcTester report which doesn't require audit to be run to see a spreadsheet view of requirements 2024-05-09 10:46:57 +10:00
Dion Moult d453adb7cf Forgot to commit IfcTester fix for missing failed entities 2024-05-09 10:36:39 +10:00
Andrej730 c5a7a8680e fix removing references in ifc2x3 #4636 2024-05-08 18:18:03 +05:00
Andrej730 d4ccadb832 fix circular import after 10d30b0de 2024-05-08 18:18:03 +05:00
Andrej730 cbf04546b8 fix many implicit ifcopenshell.guid imports
a bit related to 10d30b0de
2024-05-08 18:18:02 +05:00
Dion Moult 37c0084874 Friendlier error reporting if bbim fails to install 2024-05-08 22:26:54 +10:00
Andrej730 875afd69d5 skip objects without placements importing containers
It seems that IFC doesn't specify that IfcRelAggregates.RelatedObjects should necessary have a placement. E.g. FreeCAD is using IfcGroups in RelatedObjects to store hierarchy in IFC.

Example - https://community.osarch.org/discussion/2144/object-information-spatial-container
2024-05-08 13:47:11 +05:00
Andrej730 67f4ed00d0 fix couple silent bugs importing containers
1) we were setting OwnerHistory to the "elevation" attribute of SpatialElement (in BIMSpatialProperties.containers)
2) we were setting "elevation" attribute at all when SpatialElement doesn't have "elevation" attribute

Though those bugs were silent since PropertyGroup doesn't alarm when we add a completely new attribute to it.
2024-05-08 13:47:11 +05:00
Dion Moult 10d30b0de7 Document api modules and require explicit imports of API submodules 2024-05-08 17:00:35 +10:00
Dion Moult 984b1212a6 Whoops 2024-05-08 09:45:12 +10:00
Andrej730 bc72c927c1 replace deprecated api call
noticed fixing #4632
2024-05-07 21:41:01 +05:00
Andrej730 b8275f2802 fix errors using deprecated api after ab696b9 #4632 2024-05-07 21:39:09 +05:00
Andrej730 267527f3fc fix issue after removing ifcopenshell.main in d76462ca4 2024-05-07 21:26:48 +05:00
Ryan Schultz c5b4513f65 fix #4622 - can now reassign IfcWindowStyle and IfcDoorStyle 2024-05-07 09:46:55 -05:00
Dion Moult 5da4fbb39c Fix #4631. Fix packaging problem on PyPI for IfcPatch. 2024-05-07 23:36:56 +10:00
Dion Moult 93639e9e50 Even more cleaning of documentation references 2024-05-07 19:08:13 +10:00
Dion Moult d76462ca42 Write more documentation
Sphinx autoapi also now only shows subpackages 1 level deep. This prevents us having a huge long list. Also don't show private or special members. Also show imported members so ifcopenshell.file and ifcopenshell.entity_instance works in docs too.
2024-05-07 17:32:47 +10:00
Dion Moult 722201a1af Add py.typed for static analysis with mypy 2024-05-07 17:15:58 +10:00
Dion Moult 0a3dddef2f More Python 2 to Python 3 upgrades 2024-05-07 16:03:07 +10:00
Dion Moult 26434f0331 Fix #4589. Symlink entire ifcopenshell dir for dev setups. 2024-05-07 15:51:48 +10:00
Dion Moult f6c2e2c20d Fix #4530. Fix various styling issues on docs. 2024-05-07 15:44:06 +10:00
Dion Moult 424a06f6c8 More cleaning up of forward type hints to fix import errors 2024-05-07 14:58:12 +10:00
Dion Moult 89c4cbeb05 Drop support for Python 2. 2024-05-07 12:17:46 +10:00
Dion Moult 7e13ed746e Don't rely on util for basic ifcopenshell module capabilities. Keep util as an optional module for users to load. 2024-05-07 10:32:02 +10:00
Andrej730 28d205b4a3 show info message on saving/loading csv from bbim 2024-05-06 18:56:20 +05:00
Andrej730 dcc52fc4e4 pset.edit_pset not to fail silently on invalid enum values #4608 2024-05-06 18:22:52 +05:00
Andrej730 a9d785c28f typing 2024-05-06 18:22:52 +05:00
Bruno Postle 54a3730c20 fix d11ec67 mathutils dependency regression
mathutils is a blender module, wrap imports in try/except
2024-05-06 13:55:20 +01:00
Andrej730 31d322a28c descriptions for operators removing classifications/contexts #4614 2024-05-06 17:29:30 +05:00
Andrej730 3b90086109 Fix loading search queries with exclusion #4609
It was failing to load queries like "!IfcWall" or "!2Mg7PHubX0xxOfF0DdA9Wd"
2024-05-06 17:06:10 +05:00
Gorgious56 30b870b98d Fix #4617 : Switching representation in edit mode is no longer possible 2024-05-06 13:20:33 +02:00
Thomas Krijnen b3e7975b83 Consider material profile set in single material determination 2024-05-06 13:18:05 +02:00
Andrej730 8f7f6223de unlink pasted blender objects if there is no active ifc file #4619 2024-05-06 15:03:27 +05:00
Dion Moult 6c313dd25c Fix for static analysis of ifcopenshell.api submodules. See #2693. 2024-05-06 18:39:13 +10:00
Bruno Postle b718bd19bd fix TypeError
File "/usr/lib64/python3.12/site-packages/ifcopenshell/api/project/assign_declaration.py", line 126, in execute
    related_definitions = set(has_context.RelatedDefinitions) - objects_with_contexts
                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for -: 'set' and 'list'
2024-05-06 09:05:56 +01:00
Dion Moult ab5ea4c853 Implement listener wrapper for new API functions. See #2693. 2024-05-06 14:43:43 +10:00
Dion Moult d11ec67129 Generate functions for all API usecases for better static code features. See #2693. 2024-05-06 14:43:43 +10:00
Ryan Schultz 10f894e2ea Find the "Types" Collection regardless of the IfcProject.Name 2024-05-05 20:46:07 -05:00
Ryan Schultz 34bbc8f1ea for 'Add Fitting' changed the hot key from Shift+F to Shift+Y. per https://community.osarch.org/discussion/2124/ui-discussion-around-add-fitting-and-add-bend
Also added Shift+F (bim.flip_object) to all profile-based objects
2024-05-05 19:20:13 -05:00
Dion Moult a213ab6760 Fix docstrings to reference datatype as it would be used for users 2024-05-06 08:39:48 +10:00
Ryan Schultz d694d8fcc2 fix #4610: bim.select_type() from type launcher throws error 2024-05-05 13:16:30 -05:00
ppaawweeuu 5f2d451c5e Update selector_syntax.rst (#4618)
info about 'Must contain..' and 'Must not contain..' comparisons added
2024-05-05 23:40:32 +10:00
521 changed files with 16264 additions and 14743 deletions
+4 -2
View File
@@ -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
+146 -6
View File
@@ -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)
+2 -1
View File
@@ -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.);
+26 -12
View File
@@ -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():
+16 -12
View File
@@ -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"
+13 -1
View File
@@ -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
+11 -1
View File
@@ -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
+8 -1
View File
@@ -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
+14 -12
View File
@@ -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
+18 -3
View File
@@ -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
)
+2 -1
View File
@@ -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}"
+3 -16
View File
@@ -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
+9 -2
View File
@@ -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
+4 -3
View File
@@ -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
View File
@@ -16,6 +16,7 @@ a {
}
.sidebar-brand-text {
font-size: 1rem;
text-align: center;
}
.blockbutton {
max-width: 500px;
+6
View File
@@ -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"
},
+16 -17
View File
@@ -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"
+6 -3
View File
@@ -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.
+1
View File
@@ -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

+40 -88
View File
@@ -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:
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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":
+7 -7
View File
@@ -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,
},
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+10 -1
View File
@@ -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