mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Merge remote-tracking branch 'origin/v0.7.0' into v0.8.0
This commit is contained in:
@@ -2,6 +2,22 @@ VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py310
|
||||
PLATFORM:=linux
|
||||
|
||||
SED:=sed -i
|
||||
ifeq ($(OS),Windows_NT)
|
||||
HOSTOS:=win
|
||||
else
|
||||
UNAME_S:=$(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
HOSTOS:=linux
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
HOSTOS:=macos
|
||||
PYTHON:=python3
|
||||
PATCH:=patch -d
|
||||
SED:=sed -i '' -e
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PYVERSION), py36)
|
||||
PYNUMBER:=36
|
||||
endif
|
||||
@@ -15,7 +31,10 @@ ifeq ($(PYVERSION), py39)
|
||||
PYNUMBER:=39
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYNUMBER:=31
|
||||
PYNUMBER:=310
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
PYNUMBER:=311
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
@@ -38,6 +57,13 @@ PYNUMBER:=37
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py311)
|
||||
# Pure Evil. Let me know if you're the first to file a bug report.
|
||||
PYNUMBER:=310
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender test
|
||||
@@ -77,7 +103,7 @@ endif
|
||||
mkdir -p dist/ifcopenshell
|
||||
cp -r ifcopenshell/* dist/ifcopenshell/
|
||||
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-cdde536-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-476ab50-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell/ifcopenshell_wrapper.py dist/ifcopenshell/
|
||||
ifeq ($(PLATFORM), win)
|
||||
@@ -86,8 +112,14 @@ else
|
||||
cp -r dist/working/ifcopenshell/_ifcopenshell_wrapper.so dist/ifcopenshell/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
# distutils cannot access anything outside the cwd, so hackishly swap out the README.md
|
||||
cp README.md ../README.bak
|
||||
cp ../../README.md README.md
|
||||
$(SED) "s/999999/$(VERSION)/" pyproject.toml
|
||||
python -m build
|
||||
cd dist && mv ifcopenshell-0.7.0-py3-none-any.whl ifcopenshell-0.7.0-$(VERSION)-$(PYVERSION)-none-$(PLATFORMTAG).whl
|
||||
$(SED) "s/$(VERSION)/999999/" pyproject.toml
|
||||
mv ../README.bak README.md
|
||||
cd dist && mv ifcopenshell-0.7.0.$(VERSION)-py3-none-any.whl ifcopenshell-0.7.0.$(VERSION)-$(PYVERSION)-none-$(PLATFORMTAG).whl
|
||||
rm -rf dist/ifcopenshell
|
||||
rm -rf dist/ifcopenshell.egg-info
|
||||
rm -rf dist/ifcopenshell-0.7.0.tar.gz
|
||||
rm -rf dist/ifcopenshell-0.7.0.$(VERSION).tar.gz
|
||||
|
||||
+13
@@ -15,3 +15,16 @@ section img {
|
||||
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.field-list > dt {
|
||||
/* Clearly distinguish parameters otherwise it looks like a wall of text */
|
||||
color: var(--color-brand-content);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,10 @@ autoapi_add_toctree_entry = True
|
||||
autoapi_type = 'python'
|
||||
|
||||
# autoapi works by reading source code instead of importing modules
|
||||
autoapi_dirs = ['../ifcopenshell', '../../ifcdiff']
|
||||
autoapi_dirs = ['../ifcopenshell', '../../ifcdiff', '../../ifcpatch/ifcpatch']
|
||||
|
||||
# These are auto-generated based on the IFC schema, so exclude them
|
||||
autoapi_ignore = ['*ifcopenshell/express/rules*']
|
||||
|
||||
# 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
|
||||
@@ -93,7 +96,6 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "furo"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
@@ -102,3 +104,8 @@ html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
html_css_files = ["custom.css"]
|
||||
|
||||
# Code block styles. Dark styling helps important code examples "pop" on the
|
||||
# page even on light themes.
|
||||
pygments_style = "one-dark"
|
||||
pygments_dark_style = "one-dark"
|
||||
|
||||
@@ -1,16 +1,119 @@
|
||||
IfcClash
|
||||
========
|
||||
|
||||
This documentation is free software! You are free to contribute and help write
|
||||
this document.
|
||||
IfcClash is both a CLI utility and library that lets you perform clash detection
|
||||
on one or more IFC models. Clashes are defined in terms of clash sets with
|
||||
filters using the IFC query syntax.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
Source installation
|
||||
-------------------
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
1. :doc:`Install IfcOpenShell <ifcopenshell-python/installation>`
|
||||
2. `Install hppfcl <https://github.com/humanoid-path-planner/hpp-fcl>`_
|
||||
3. Optionally `install bcf <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf>`_ (needed for BCF reports of results)
|
||||
4. `Clone the source code <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/ifcclash>`_.
|
||||
5. ``cd /path/to/src/ifcclash``
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
Here is a minimal example of how to use IfcPatch as a Python module or CLI
|
||||
utility:
|
||||
|
||||
::
|
||||
|
||||
$ python -m ifcclash -h
|
||||
|
||||
usage: __main__.py [-h] [-o OUTPUT] input
|
||||
|
||||
Clashes geometry between two IFC files
|
||||
|
||||
positional arguments:
|
||||
input A JSON dataset describing a series of clashsets
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-o OUTPUT, --output OUTPUT
|
||||
The JSON diff file to output. Defaults to output.json
|
||||
|
||||
Instructions on what clashes to perform are structured in terms of clash sets.
|
||||
Each clash set contains instructions of collisions that we want to perform, and
|
||||
can be named so it is easy to distinguish. A typical name would be "Structure
|
||||
and Pipes", to describe that we are are detecting collisions between structural
|
||||
elements and pipes.
|
||||
|
||||
Each clash set may include two groups of objects, named ``A`` and ``B``. This
|
||||
tells IfcClash to attempt to find collisions between any object in group ``A``
|
||||
with any object in group ``B``. Group ``A`` is mandatory, but group ``B`` is
|
||||
optional. If group ``B`` is not provided, IfcClash will detect all clashes
|
||||
within objects of group ``A``.
|
||||
|
||||
Within group ``A`` or ``B``, you may define one or more data sources of objects.
|
||||
A data source must include a path to the IFC file which the objects come from.
|
||||
You may also optionally provide a filter to only include or exclude certain
|
||||
objects. If no filter is provided, then all objects will be used to detect
|
||||
collisions.
|
||||
|
||||
Here's a sample JSON description of a single clash set, with both groups
|
||||
defined with data sources.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "Clash Set A",
|
||||
"a": [
|
||||
{
|
||||
"file": "/path/to/one.ifc"
|
||||
}
|
||||
],
|
||||
"b": [
|
||||
{
|
||||
"file": "/path/to/two.ifc",
|
||||
"selector": ".IfcWall",
|
||||
"mode": "i"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Once your have your JSON description of your clashes, usage is like any other
|
||||
CLI app.
|
||||
|
||||
::
|
||||
|
||||
$ ifcclash clash_sets.json
|
||||
$ cat output.json
|
||||
|
||||
Here is a minimal example of how to use IfcClash as a library:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import ifcclash
|
||||
|
||||
|
||||
settings = ClashSettings()
|
||||
settings.output = "output.json"
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
settings.logger.addHandler(handler)
|
||||
ifc_clasher = Clasher(settings)
|
||||
with open(args.input, "r") as clash_sets_file:
|
||||
ifc_clasher.clash_sets = json.loads(clash_sets_file.read())
|
||||
ifc_clasher.clash()
|
||||
ifc_clasher.export()
|
||||
|
||||
You can also alias it to a command:
|
||||
|
||||
::
|
||||
|
||||
$ alias ifcclash='python -m ifcclash'
|
||||
|
||||
Alternatively, you can package it as an executable.
|
||||
|
||||
::
|
||||
|
||||
$ python make.py
|
||||
$ ./dist/ifcclash
|
||||
|
||||
@@ -20,11 +20,11 @@ Pre-built packages
|
||||
| build-linux64_ | build-win32_ | build-win64_ | build-macos64_ | build-macosm164_ |
|
||||
+----------------+----------------+----------------+----------------+------------------+
|
||||
|
||||
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-cdde536-linux64.zip
|
||||
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-cdde536-win32.zip
|
||||
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-cdde536-win64.zip
|
||||
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-cdde536-macos64.zip
|
||||
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-cdde536-macosm164.zip
|
||||
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-476ab50-linux64.zip
|
||||
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-476ab50-win32.zip
|
||||
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-476ab50-win64.zip
|
||||
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-476ab50-macos64.zip
|
||||
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-476ab50-macosm164.zip
|
||||
|
||||
2. Unzip the downloaded file and run IfcConvert using the command line.
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
IfcMax
|
||||
======
|
||||
|
||||
This documentation is free software! You are free to contribute and help write
|
||||
this document.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
@@ -148,10 +148,15 @@ Convert to and from SI units and project units
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
# Note: ifc_project_length is a value you have extracted from the project,
|
||||
# just as from a quantity set.
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(model)
|
||||
# Convert to SI unit:
|
||||
si_meters = ifc_project_length * unit_scale
|
||||
# Convert from SI unit:
|
||||
ifc_project_length = si_meters / unit_scale
|
||||
|
||||
|
||||
Get the distribution system of an element
|
||||
-----------------------------------------
|
||||
|
||||
@@ -169,25 +174,25 @@ Get the distribution system of an element
|
||||
Copy an entity instance
|
||||
-----------------------------------------
|
||||
|
||||
Copy an entity instance is possible in different ways, depending on the task.
|
||||
Copy an entity instance is possible in different ways, depending on the task.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ifcopenshell.api.run("root.copy_class", ifc_file, product = entity_instance_to_copy)
|
||||
wall_copy_class = ifcopenshell.api.run("root.copy_class", model, product = wall)
|
||||
|
||||
This is high level and makes sensible assumptions about copying things like properties, quantities, openings, and other relationships.
|
||||
This is high level and makes sensible assumptions about copying things like properties and quantities. It does not copy the element's representation, however.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ifcopenshell.util.element.copy(ifc_file, element)
|
||||
wall_shallow_copy = ifcopenshell.util.element.copy(model, wall)
|
||||
|
||||
This is for shallow copies.
|
||||
This is for shallow copies. That is, associated things like the element's type, materials, and properties are not copied. The new element, however, has the same representation and placement as the original.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ifcopenshell.util.element.copy_deep(ifc_file, element, exclude = None)
|
||||
wall_deepgraph_copy = ifcopenshell.util.element.copy_deep(model, wall, exclude = None)
|
||||
|
||||
This is for deep graph copy.
|
||||
This is for deep graph copy. Like shallow copy, it does not copy over things like associated type/properties/quantities, but it does copy the representation and placement.
|
||||
|
||||
Also note that ifcopenshell.file.add() can be used to copy instances from one file to the other.
|
||||
|
||||
@@ -197,7 +202,7 @@ Also note that ifcopenshell.file.add() can be used to copy instances from one fi
|
||||
g = ifcopenshell.file(schema=f.schema)
|
||||
g.add(f.by_type(...)[0])
|
||||
|
||||
Note that, in this case, it does copy over recursively, factor in length unit conversion if both files f and g have project length unit defined, but it does not make any other attempts at resulting in a valid file.
|
||||
Note that, in this case, it does copy over recursively, however, it does not make any other attempts at resulting in a valid file. Factor in things like length unit conversion if both files (f and g) have project length unit defined.
|
||||
|
||||
Create a simple model from scratch
|
||||
----------------------------------
|
||||
@@ -219,11 +224,10 @@ Create a simple model from scratch
|
||||
|
||||
# Let's create a modeling geometry context, so we can store 3D geometry (note: IFC supports 2D too!)
|
||||
context = run("context.add_context", model, context_type="Model")
|
||||
|
||||
# In particular, in this example we want to store the 3D "body" geometry of objects, i.e. the body shape
|
||||
body = run(
|
||||
"context.add_context", model,
|
||||
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=context
|
||||
)
|
||||
body = run("context.add_context", model, context_type="Model",
|
||||
context_identifier="Body", target_view="MODEL_VIEW", parent=context)
|
||||
|
||||
# Create a site, building, and storey. Many hierarchies are possible.
|
||||
site = run("root.create_entity", model, ifc_class="IfcSite", name="My Site")
|
||||
@@ -252,3 +256,73 @@ Create a simple model from scratch
|
||||
Here is the result:
|
||||
|
||||
.. image:: images/simple-model.png
|
||||
|
||||
|
||||
Create a work schedule constructing a building floor by floor
|
||||
-------------------------------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import datetime
|
||||
import ifcopenshell
|
||||
from ifcopenshell.api import run
|
||||
from ifcopenshell.util.element import get_decomposition
|
||||
from ifcopenshell.util.placement import get_storey_elevation
|
||||
|
||||
# Define a convenience function to add a task chained to a predecessor
|
||||
def add_task(model, name, predecessor, work_schedule):
|
||||
# Add a construction task
|
||||
task = run("sequence.add_task", model,
|
||||
work_schedule=work_schedule, name=name, predefined_type="CONSTRUCTION")
|
||||
|
||||
# Give it a time
|
||||
task_time = run("sequence.add_task_time", model, task=task)
|
||||
|
||||
# Arbitrarily set the task's scheduled time duration to be 1 week
|
||||
run("sequence.edit_task_time", model, task_time=task_time,
|
||||
attributes={"ScheduleStart": datetime.date(2000, 1, 1), "ScheduleDuration": "P1W"})
|
||||
|
||||
# If a predecessor exists, create a finish to start relationship
|
||||
if predecessor:
|
||||
run("sequence.assign_sequence", model, relating_process=predecessor, related_process=task)
|
||||
|
||||
return task
|
||||
|
||||
# Open an existing IFC4 model you have of a building
|
||||
model = ifcopenshell.open("/path/to/existing/model.ifc")
|
||||
|
||||
# Create a new construction schedule
|
||||
schedule = run("sequence.add_work_schedule", model, name="Construction")
|
||||
|
||||
# Let's imagine a starting task for site establishment.
|
||||
task = add_task(model, "Site establishment", None, schedule)
|
||||
start_task = task
|
||||
|
||||
# Get all our storeys sorted by elevation ascending.
|
||||
storeys = sorted(model.by_type("IfcBuildingStorey"), key=lambda s: get_storey_elevation(s))
|
||||
|
||||
# For each storey ...
|
||||
for storey in storeys:
|
||||
|
||||
# Add a construction task to construct that storey, using our convenience function
|
||||
task = add_task(model, f"Construct {storey.Name}", task, schedule)
|
||||
|
||||
# Assign all the products in that storey to the task as construction outputs.
|
||||
for product in get_decomposition(storey):
|
||||
run("sequence.assign_product", model, relating_product=product, related_object=task)
|
||||
|
||||
# Ask the computer to calculate all the dates for us from the start task.
|
||||
# For example, if the first task started on the 1st of January and took a
|
||||
# week, the next task will start on the 8th of January. This saves us
|
||||
# manually doing date calculations.
|
||||
run("sequence.cascade_schedule", model, task=start_task)
|
||||
|
||||
# Calculate the critical path and floats.
|
||||
run("sequence.recalculate_schedule", model, work_schedule=schedule)
|
||||
|
||||
# Write out to a file
|
||||
model.write("/home/dion/model.ifc")
|
||||
|
||||
Here is the result:
|
||||
|
||||
.. image:: images/simple-work-schedule.png
|
||||
|
||||
@@ -76,7 +76,7 @@ related information in ``shape.geometry``:
|
||||
# Each style is named after the entity class if a default
|
||||
# material is applied. Otherwise, it is named "surface-style-{SurfaceStyle.name}"
|
||||
# All non-alphanumeric characters are replaced with a "-".
|
||||
print(style.original_name)
|
||||
print(style.original_name())
|
||||
|
||||
# A more human readable name
|
||||
print(style.name)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -8,52 +8,62 @@ There are different methods of installation, depending on your situation.
|
||||
3. **Conda** is recommended for developers using Anaconda.
|
||||
4. **Docker** is recommended for developers using Docker.
|
||||
5. **Using the BlenderBIM Add-on** is recommended for non-developers wanting a graphical interface.
|
||||
6. **Compiling from source** is recommended for developers actively working with the C++ core.
|
||||
6. **From source with precompiled binaries** is recommended for developers actively working with the Python code.
|
||||
7. **Compiling from source** is recommended for developers actively working with the C++ core.
|
||||
|
||||
Pre-built packages
|
||||
------------------
|
||||
|
||||
Pre-built packages are prepared sporadically depending on whether there are
|
||||
changes in the IfcOpenShell C++ core.
|
||||
|
||||
1. Choose which version to download based on your operating system, Python
|
||||
version, and computer architecture.
|
||||
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
| | Linux 64bit | Windows 32bit | Windows 64bit | MacOS 64bit | MacOS M1 64bit |
|
||||
+=============+================+================+================+================+=================+
|
||||
| Python 3.6 | py36-linux64_ | py36-win32_ | py36-win64_ | py36-macos64_ | N/A |
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
| Python 3.7 | py37-linux64_ | py37-win32_ | py37-win64_ | py37-macos64_ | py37-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
| Python 3.8 | py38-linux64_ | py38-win32_ | py38-win64_ | py38-macos64_ | py38-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
| Python 3.9 | py39-linux64_ | py39-win32_ | py39-win64_ | py39-macos64_ | py39-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
| Python 3.10 | py31-linux64_ | py31-win32_ | py31-win64_ | py31-macos64_ | py31-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+-----------------+
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| | Linux 64bit | Windows 32bit | Windows 64bit | MacOS 64bit | MacOS M1 64bit |
|
||||
+=============+================+================+================+================+==================+
|
||||
| Python 3.6 | py36-linux64_ | py36-win32_ | py36-win64_ | py36-macos64_ | N/A |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.7 | py37-linux64_ | py37-win32_ | py37-win64_ | py37-macos64_ | py37-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.8 | py38-linux64_ | py38-win32_ | py38-win64_ | py38-macos64_ | py38-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.9 | py39-linux64_ | py39-win32_ | py39-win64_ | py39-macos64_ | py39-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.10 | py310-linux64_ | py310-win32_ | py310-win64_ | py310-macos64_ | py310-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
| Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
|
||||
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-cdde536-linux64.zip
|
||||
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-cdde536-linux64.zip
|
||||
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-cdde536-linux64.zip
|
||||
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-cdde536-linux64.zip
|
||||
.. _py31-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-31-v0.7.0-cdde536-linux64.zip
|
||||
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-cdde536-win64.zip
|
||||
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-cdde536-win64.zip
|
||||
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-cdde536-win64.zip
|
||||
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-cdde536-win64.zip
|
||||
.. _py31-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-31-v0.7.0-cdde536-win64.zip
|
||||
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-cdde536-win64.zip
|
||||
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-cdde536-win64.zip
|
||||
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-cdde536-win64.zip
|
||||
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-cdde536-win64.zip
|
||||
.. _py31-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-31-v0.7.0-cdde536-win64.zip
|
||||
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-cdde536-macos64.zip
|
||||
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-cdde536-macos64.zip
|
||||
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-cdde536-macos64.zip
|
||||
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-cdde536-macos64.zip
|
||||
.. _py31-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-31-v0.7.0-cdde536-macos64.zip
|
||||
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-cdde536-macosm164.zip
|
||||
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-cdde536-macosm164.zip
|
||||
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-cdde536-macosm164.zip
|
||||
.. _py31-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-31-v0.7.0-cdde536-macosm164.zip
|
||||
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-476ab50-linux64.zip
|
||||
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-476ab50-linux64.zip
|
||||
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-476ab50-linux64.zip
|
||||
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-476ab50-linux64.zip
|
||||
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-476ab50-linux64.zip
|
||||
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-476ab50-linux64.zip
|
||||
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-476ab50-win64.zip
|
||||
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-476ab50-win64.zip
|
||||
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-476ab50-win64.zip
|
||||
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-476ab50-win64.zip
|
||||
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-476ab50-win64.zip
|
||||
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-476ab50-win64.zip
|
||||
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-476ab50-win64.zip
|
||||
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-476ab50-win64.zip
|
||||
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-476ab50-win64.zip
|
||||
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-476ab50-win64.zip
|
||||
.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-476ab50-win64.zip
|
||||
.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-476ab50-win64.zip
|
||||
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-476ab50-macos64.zip
|
||||
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-476ab50-macos64.zip
|
||||
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-476ab50-macos64.zip
|
||||
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-476ab50-macos64.zip
|
||||
.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-476ab50-macos64.zip
|
||||
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-476ab50-macosm164.zip
|
||||
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-476ab50-macosm164.zip
|
||||
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-476ab50-macosm164.zip
|
||||
.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-476ab50-macosm164.zip
|
||||
.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-476ab50-macosm164.zip
|
||||
|
||||
2. Unzip the downloaded file and copy the ``ifcopenshell`` directory into your
|
||||
Python path. If you're not sure where your Python path is, run the following
|
||||
@@ -79,6 +89,12 @@ Pre-built packages
|
||||
PyPI
|
||||
----
|
||||
|
||||
PyPI releases are automatically performed once a month.
|
||||
|
||||
Releases on PyPI may potentially ship slightly outdated precompiled binaires of
|
||||
the C++ core. This is because the binaries typically go through a period of
|
||||
manual testing prior in case of high-risk changes.
|
||||
|
||||
.. code-block::
|
||||
|
||||
pip install ifcopenshell
|
||||
@@ -124,6 +140,8 @@ need to install Python first, and you also can compare your IfcOpenShell
|
||||
scripting to what you see with a visual model viewer, or use a graphical
|
||||
interface to access the IfcOpenShell utilities.
|
||||
|
||||
The BlenderBIM Add-on is available either as a stable build or a daily build.
|
||||
|
||||
1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on
|
||||
installation documentation
|
||||
<https://blenderbim.org/docs/users/installation.html>`_.
|
||||
@@ -162,6 +180,17 @@ and run your script using the **Text > Run Script** menu or by clicking on the
|
||||
interface. `Read more
|
||||
<https://blenderbim.org/docs/users/exploring_an_ifc_model.html>`_.
|
||||
|
||||
From source with precompiled binaries
|
||||
-------------------------------------
|
||||
|
||||
1. Clone or download the `IfcOpenShell repository
|
||||
<https://github.com/ifcopenshell/ifcopenshell>`_.
|
||||
|
||||
2. Place ``src/ifcopenshell-python/ifcopenshell/`` in your Python path.
|
||||
|
||||
3. Download the relevant pre-built package, and copy over the two files with
|
||||
``ifcopenshell_wrapper`` in their filename to
|
||||
``src/ifcopenshell-python/ifcopenshell/``.
|
||||
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
@@ -16,6 +16,7 @@ compile-time when using C++ and at run-time when using Python.
|
||||
ifcopenshell/installation
|
||||
ifcopenshell/geometry_iterator
|
||||
ifcopenshell/geometry_settings
|
||||
ifcopenshell/boolean_process
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Boolean process
|
||||
===============
|
||||
|
||||
Booleans are one of the more complex aspects of 3D geometry processing. IfcOpenShell orchestrates a variety of OpenCASCADE commands to ensure that booleans (whether through IFC openings or through IFC representation booleans) are processed reliably:
|
||||
|
||||
.. image:: images/boolean-process.jpg
|
||||
@@ -530,6 +530,27 @@ In Python, this is set when the iterator is constructed:
|
||||
import multiprocessing
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, num_threads=multiprocessing.cpu_count())
|
||||
|
||||
offset
|
||||
------
|
||||
|
||||
+---------------+--------------------+---------+
|
||||
| Type | IfcConvert Option | Default |
|
||||
+===============+====================+=========+
|
||||
| ARRAY<DOUBLE> | ``--model-offset`` | 0,0,0 |
|
||||
+---------------+--------------------+---------+
|
||||
|
||||
Sets an offset to be applied to all the matrixes of geometries returned from
|
||||
the iterator.
|
||||
|
||||
In Python, this is set in the settings passed to the iterator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
offset[0], offset[1], offset[2] = (1, 2, 3)
|
||||
settings.offset = offset
|
||||
|
||||
SEW_SHELLS
|
||||
----------
|
||||
|
||||
@@ -562,7 +583,9 @@ STRICT_TOLERANCE
|
||||
+------+------------------------+---------+
|
||||
|
||||
Strictly use the tolerance from the IFC model. Typically this value is increased
|
||||
10-fold to have more reliable boolean subtraction results.
|
||||
10-fold to have more reliable boolean subtraction results. It is recommended to
|
||||
always have this set to True and should only be set to False for backwards
|
||||
compatibility.
|
||||
|
||||
USE_BREP_DATA
|
||||
-------------
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 690 KiB |
@@ -50,14 +50,15 @@ In this example, we'll extract out all `IfcWall` elements.
|
||||
$ ifcpatch -i input.ifc -o output.ifc -r ExtractElements -a ".IfcWall"
|
||||
$ cat output.ifc
|
||||
|
||||
Here is a minimal example of how to use IfcDiff as a library:
|
||||
Here is a minimal example of how to use IfcPatch as a library:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ifcpatch
|
||||
|
||||
output = ifcpatch.execute({
|
||||
"input": ifcopenshell.open("input.ifc"),
|
||||
"input": "input.ifc",
|
||||
"file": ifcopenshell.open("input.ifc"),
|
||||
"recipe": "ExtractElements",
|
||||
"arguments": [".IfcWall"],
|
||||
})
|
||||
@@ -75,3 +76,8 @@ Alternatively, you can package it as an executable.
|
||||
|
||||
$ python make.py
|
||||
$ ./dist/ifcpatch
|
||||
|
||||
Patch recipes
|
||||
-------------
|
||||
|
||||
You can view all built-in patches in IfcPatch here: :doc:`List of IfcPatch recipes <autoapi/ifcpatch/recipes/index>`.
|
||||
|
||||
@@ -20,7 +20,6 @@ IfcOpenShell is a suite of developer libraries and utilities to manipulate OpenB
|
||||
ifcpatch
|
||||
ifcclash
|
||||
ifccobie
|
||||
ifcmax
|
||||
ifcsverchok
|
||||
bimserver-plugin
|
||||
C++ API Reference <https://ifcopenshell.github.io/docs/rst_files/library_root.html>
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
Typically used for opening an IFC via a filepath, or accessing one of the
|
||||
submodules.
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import ifcopenshell
|
||||
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
||||
@@ -34,6 +36,11 @@ from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import ifcopenshell.util.file
|
||||
|
||||
if hasattr(os, "uname"):
|
||||
platform_system = os.uname()[0].lower()
|
||||
@@ -80,19 +87,34 @@ class SchemaError(Error):
|
||||
pass
|
||||
|
||||
|
||||
def open(fn):
|
||||
def open(path: "os.PathLike | str", format: str = None) -> file:
|
||||
"""Loads an IFC dataset from a filepath
|
||||
|
||||
:param fn: Filepath to the IFC model
|
||||
:type fn: string
|
||||
:returns: A file object
|
||||
:rtype: ifcopenshell.file.file
|
||||
You can specify a file format. If no format is given, it is guessed from its extension.
|
||||
Currently supported specified format : .ifc | .ifcZIP | .ifcXML
|
||||
|
||||
Example::
|
||||
|
||||
ifc_file = ifcopenshell.open("/path/to/model.ifc")
|
||||
Examples:
|
||||
model = ifcopenshell.open("/path/to/model.ifc")
|
||||
model = ifcopenshell.open("/path/to/model.ifcXML")
|
||||
model = ifcopenshell.open("/path/to/model.any_extension", ".ifc")
|
||||
"""
|
||||
f = ifcopenshell_wrapper.open(os.path.abspath(fn))
|
||||
path = Path(path)
|
||||
if format is None:
|
||||
format = ifcopenshell.util.file.guess_format(path)
|
||||
if format == ".ifcXML":
|
||||
f = ifcopenshell_wrapper.parse_ifcxml(str(path.absolute()))
|
||||
if f:
|
||||
return file(f)
|
||||
raise IOError(f"Failed to parse .ifcXML file from {path}")
|
||||
if format == ".ifcZIP":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
for name in zf.namelist():
|
||||
if Path(name).suffix.lower() in (".ifc", ".ifcxml"):
|
||||
return open(zf.extract(name, unzipped_path))
|
||||
else:
|
||||
raise LookupError(f"No .ifc or .ifcXML file found in {path}")
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()))
|
||||
if f.good():
|
||||
return file(f)
|
||||
else:
|
||||
@@ -101,7 +123,8 @@ def open(fn):
|
||||
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)
|
||||
@@ -122,7 +145,9 @@ def create_entity(type, schema="IFC4", *args, **kwargs):
|
||||
:returns: An entity instance
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
person = ifcopenshell.create_entity("IfcPerson") # #0=IfcPerson($,$,$,$,$,$,$,$)
|
||||
model = ifcopenshell.file()
|
||||
@@ -144,7 +169,9 @@ def register_schema(schema):
|
||||
:param schema: A schema object
|
||||
:type schema: ifcopenshell.express.schema_class.SchemaClass
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schema = ifcopenshell.express.parse("/path/to/ifc-custom.exp")
|
||||
ifcopenshell.register_schema(schema)
|
||||
|
||||
@@ -57,20 +57,24 @@ class Usecase:
|
||||
|
||||
:param product: The part of the aggregate, typically an IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:type product: 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
|
||||
:type relating_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. 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")
|
||||
|
||||
# The project contains a site (note that project aggregation is a special case in IFC)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, product=element, relating_object=project)
|
||||
|
||||
# The site has a building
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, product=subelement, relating_object=element)
|
||||
"""
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
products = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.products = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
if not file:
|
||||
return
|
||||
product = file.by_id(product_id)
|
||||
if product.Decomposes and product.Decomposes[0].is_a("IfcRelAggregates"):
|
||||
obj = product.Decomposes[0].RelatingObject
|
||||
cls.products[product_id] = {"type": obj.is_a(), "Name": obj.Name, "id": int(obj.id())}
|
||||
else:
|
||||
cls.products[product_id] = {"type": None, "Name": None, "id": None}
|
||||
@@ -38,12 +38,14 @@ class Usecase:
|
||||
|
||||
:param product: The part of the aggregate, typically an IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance, only returned if the
|
||||
whole still contains any other parts.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, None
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
import ifcopenshell.util.attribute
|
||||
|
||||
|
||||
class Data:
|
||||
products = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.products = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
if not file:
|
||||
return
|
||||
product = file.by_id(product_id)
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
|
||||
cls.products[product_id] = []
|
||||
declaration = schema.declaration_by_name(product.is_a())
|
||||
for attribute in declaration.all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
value = getattr(product, attribute.name())
|
||||
list_type = None
|
||||
enum_items = ()
|
||||
|
||||
if isinstance(data_type, tuple):
|
||||
list_type = data_type[1]
|
||||
data_type = data_type[0]
|
||||
|
||||
if data_type in ["entity", "list", "string", "enum"]:
|
||||
value = None if value is None else str(value)
|
||||
elif data_type == "float":
|
||||
value = None if value is None else float(value)
|
||||
elif data_type == "integer":
|
||||
value = None if value is None else int(value)
|
||||
|
||||
if data_type == "enum":
|
||||
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
|
||||
|
||||
cls.products[product_id].append(
|
||||
{
|
||||
"name": attribute.name(),
|
||||
"value": value,
|
||||
"type": data_type,
|
||||
"enum_items": enum_items,
|
||||
"list_type": list_type,
|
||||
"is_optional": attribute.optional(),
|
||||
"is_null": getattr(product, attribute.name()) is None,
|
||||
}
|
||||
)
|
||||
@@ -30,13 +30,15 @@ class Usecase:
|
||||
|
||||
:param product: The product you want to edit. This may be any rooted IFC
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
ifcopenshell.api.run("attribute.edit_attributes", model,
|
||||
|
||||
@@ -20,19 +20,66 @@ import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **kwargs):
|
||||
"""location, axis and ref_direction defines the plane"""
|
||||
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 = None
|
||||
self.outer_boundary = None
|
||||
self.inner_boundaries = ()
|
||||
self.location = None
|
||||
self.axis = None
|
||||
self.ref_direction = None
|
||||
self.unit_scale = None
|
||||
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 = []
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def execute(self):
|
||||
if self.unit_scale is None:
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
boundaries = {}
|
||||
spaces = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.boundaries = {}
|
||||
cls.spaces = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
cls._file = file
|
||||
for boundary in cls._file.by_type("IfcRelSpaceBoundary"):
|
||||
data = boundary.get_info()
|
||||
data["RelatingSpace"] = data["RelatingSpace"].id() if data["RelatingSpace"] else None
|
||||
data["RelatedBuildingElement"] = (
|
||||
data["RelatedBuildingElement"].id() if data["RelatedBuildingElement"] else None
|
||||
)
|
||||
del data["ConnectionGeometry"]
|
||||
if cls._file.schema == "IFC2X3":
|
||||
pass
|
||||
else:
|
||||
if boundary.is_a("IfcRelSpaceBoundary1stLevel"):
|
||||
data["ParentBoundary"] = data["ParentBoundary"].id() if data["ParentBoundary"] else None
|
||||
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
|
||||
data["CorrespondingBoundary"] = (
|
||||
data["CorrespondingBoundary"].id() if data["CorrespondingBoundary"] else None
|
||||
)
|
||||
cls.boundaries[boundary.id()] = data
|
||||
cls.spaces.setdefault(data["RelatingSpace"], []).append(boundary.id())
|
||||
cls.is_loaded = True
|
||||
@@ -18,16 +18,39 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **kwargs):
|
||||
"""location, axis and ref_direction defines the plane"""
|
||||
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
|
||||
|
||||
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: "IfcRelSpaceBoundary"
|
||||
self.relating_space: "IfcSpace | IfcExternalSpatialElement"
|
||||
self.related_building_element: "IfcElement"
|
||||
self.parent_boundary: "IfcRelSpaceBoundary" = None
|
||||
self.corresponding_boundary: "IfcRelSpaceBoundary" = None
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
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
|
||||
|
||||
def execute(self):
|
||||
self.entity.RelatingSpace = self.relating_space
|
||||
|
||||
@@ -22,13 +22,64 @@ import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, classification=None):
|
||||
"""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": None,
|
||||
"classification": classification,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if isinstance(self.settings["classification"], str):
|
||||
|
||||
@@ -21,18 +21,101 @@ import ifcopenshell.util.schema
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, reference=None, identification=None, name=None, classification=None, is_lightweight=True):
|
||||
"""Adds a new classification reference and assigns it to a product
|
||||
|
||||
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 the 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 IFC object, property, or resource you want to
|
||||
associate the classification reference to.
|
||||
:type product: 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 product: 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 product: 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
|
||||
:return: The newly added IfcClassificationReference
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
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,
|
||||
product=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,
|
||||
product=wall_type, classification=classification,
|
||||
reference=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"reference": None,
|
||||
"identification": None,
|
||||
"name": None,
|
||||
"classification": None,
|
||||
"is_lightweight": True,
|
||||
"product": product,
|
||||
"reference": reference,
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"classification": classification,
|
||||
"is_lightweight": is_lightweight,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.is_rooted = self.settings["product"].is_a("IfcRoot")
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
products = {}
|
||||
classifications = {}
|
||||
references = {}
|
||||
library_file = None
|
||||
library_classifications = {}
|
||||
library_references = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.products = {}
|
||||
cls.classifications = {}
|
||||
cls.references = {}
|
||||
cls.library_file = None
|
||||
cls.library_classifications = {}
|
||||
cls.library_references = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id=None):
|
||||
cls._file = file
|
||||
if not cls._file:
|
||||
return
|
||||
if product_id:
|
||||
return cls.load_product_classifications(product_id)
|
||||
cls.load_classifications()
|
||||
cls.load_references()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def load_product_classifications(cls, product_id):
|
||||
product = cls._file.by_id(product_id)
|
||||
cls.products[product_id] = []
|
||||
if not product.HasAssociations:
|
||||
return
|
||||
for association in product.HasAssociations:
|
||||
if association.is_a("IfcRelAssociatesClassification"):
|
||||
cls.products[product_id].append(association.RelatingClassification.id())
|
||||
|
||||
@classmethod
|
||||
def load_classifications(cls):
|
||||
cls.classifications = {}
|
||||
for classification in cls._file.by_type("IfcClassification"):
|
||||
data = classification.get_info()
|
||||
if cls._file.schema == "IFC2X3" and data["EditionDate"]:
|
||||
data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"]).isoformat()
|
||||
cls.classifications[classification.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_references(cls):
|
||||
cls.references = {}
|
||||
for reference in cls._file.by_type("IfcClassificationReference"):
|
||||
data = reference.get_info()
|
||||
if reference.ReferencedSource:
|
||||
# data["ReferencedSource"] = cls.get_referenced_source(reference.ReferencedSource)
|
||||
data["ReferencedSource"] = reference.ReferencedSource.id()
|
||||
cls.references[reference.id()] = data
|
||||
|
||||
@classmethod
|
||||
def get_referenced_source(cls, reference):
|
||||
if reference.is_a("IfcClassification"):
|
||||
return reference
|
||||
elif reference.is_a("IfcClassificationReference") and reference.ReferencedSource:
|
||||
return cls.get_referenced_source(reference.ReferencedSource)
|
||||
|
||||
@classmethod
|
||||
def load_library(cls, filepath):
|
||||
cls.library_file = ifcopenshell.open(filepath)
|
||||
cls.library_classifications = {}
|
||||
for classification in cls.library_file.by_type("IfcClassification"):
|
||||
cls.library_classifications[classification.id()] = classification.Name
|
||||
@@ -18,11 +18,30 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, classification=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassification
|
||||
|
||||
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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"classification": classification, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,30 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, reference=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
|
||||
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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"reference": reference, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,28 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
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": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"classification": classification}
|
||||
|
||||
def execute(self):
|
||||
references = self.get_references(self.settings["classification"])
|
||||
|
||||
@@ -18,11 +18,36 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, reference=None, product=None):
|
||||
"""Removes a classification reference from a product
|
||||
|
||||
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 object entity of the relationship you want to
|
||||
remove.
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. 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,
|
||||
product=wall_type, classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
ifcopenshell.api.run("classification.remove_reference", model,
|
||||
reference=reference, product=wall_type)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": None, "product": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"reference": reference, "product": product}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcRoot"):
|
||||
|
||||
@@ -20,13 +20,31 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, objective=None):
|
||||
"""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.
|
||||
|
||||
: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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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": None,
|
||||
"objective": objective,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
metric = self.file.create_entity(
|
||||
|
||||
@@ -20,11 +20,31 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file):
|
||||
"""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.
|
||||
|
||||
:return: The newly created IfcObjective entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. 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 = {}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(
|
||||
|
||||
@@ -20,14 +20,30 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, constraint=None):
|
||||
"""Assigns a constraint to a product
|
||||
|
||||
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 product: The product the constraint applies to. This is anything
|
||||
which can have properties or quantities.
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The new or updated IfcRelAssociatesConstraint relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"constraint": None,
|
||||
"product": product,
|
||||
"constraint": constraint,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
rel = self.get_constraint_rel()
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
products = {}
|
||||
objectives = {}
|
||||
metrics = {}
|
||||
references = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.products = {}
|
||||
cls.objectives = {}
|
||||
cls.metrics = {}
|
||||
cls.references = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id=None):
|
||||
cls._file = file
|
||||
if not cls._file:
|
||||
return
|
||||
if product_id:
|
||||
return cls.load_product(product_id)
|
||||
cls.load_objectives()
|
||||
cls.load_metrics()
|
||||
cls.load_references()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def load_product(cls, product_id):
|
||||
product = cls._file.by_id(product_id)
|
||||
cls.products[product_id] = []
|
||||
if not product.HasAssociations:
|
||||
return
|
||||
for association in product.HasAssociations:
|
||||
if association.is_a("IfcRelAssociatesConstraint"):
|
||||
if not association.RelatingConstraint.is_a("IfcObjective"):
|
||||
continue # not yet implemented
|
||||
cls.products[product_id].append(association.RelatingConstraint.id())
|
||||
|
||||
@classmethod
|
||||
def load_objectives(cls):
|
||||
cls.objectives = {}
|
||||
for constraint in cls._file.by_type("IfcObjective"):
|
||||
data = constraint.get_info()
|
||||
for key, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if cls._file.schema == "IFC2X3":
|
||||
for attribute in ["CreationTime"]:
|
||||
if data[attribute]:
|
||||
data[attribute] = ifcopenshell.util.date.ifc2datetime(data[attribute]).isoformat()
|
||||
data["BenchmarkValues"] = [metric.id() for metric in constraint.BenchmarkValues or []]
|
||||
cls.objectives[constraint.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_metrics(cls):
|
||||
cls.metrics = {}
|
||||
for metric in cls._file.by_type("IfcMetric"):
|
||||
data = metric.get_info()
|
||||
for key, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
data["ConstrainedObjects"] = []
|
||||
for association in cls._file.by_type("IfcRelAssociatesConstraint"):
|
||||
if association.RelatingConstraint.id() == metric.id():
|
||||
data["ConstrainedObjects"] = [o.id() for o in association.RelatedObjects or []]
|
||||
if metric.DataValue:
|
||||
data["DataValue"] = data["DataValue"].id()
|
||||
if metric.ReferencePath:
|
||||
data["ReferencePath"] = data["ReferencePath"].id()
|
||||
cls.metrics[metric.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_references(cls):
|
||||
cls.references = {}
|
||||
for reference in cls._file.by_type("IfcReference"):
|
||||
data = reference.get_info()
|
||||
for key, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
cls.references[refenrece.id()] = data
|
||||
@@ -18,11 +18,31 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, metric=None, attributes=None):
|
||||
"""Edit the attributes of a metric
|
||||
|
||||
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
|
||||
|
||||
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.edit_metric", model,
|
||||
metric=metric, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"metric": metric, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, objective=None, attributes=None):
|
||||
"""Edit the attributes of a objective
|
||||
|
||||
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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"objective": objective, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, constraint=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.
|
||||
|
||||
:param constraint: The IfcObjective you want to remove.
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. 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": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"constraint": constraint}
|
||||
|
||||
def execute(self):
|
||||
self.file.remove(self.settings["constraint"])
|
||||
|
||||
@@ -18,11 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
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": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"metric": metric}
|
||||
|
||||
def execute(self):
|
||||
self.file.remove(self.settings["metric"])
|
||||
|
||||
@@ -18,14 +18,24 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, constraint=None):
|
||||
"""Unassigns a constraint to a product
|
||||
|
||||
The constraint will not be deleted and is available to be assigned to
|
||||
other products.
|
||||
|
||||
:param product: The product the constraint applies to.
|
||||
:type product: 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 = {
|
||||
"product": None,
|
||||
"constraint": None,
|
||||
"product": product,
|
||||
"constraint": constraint,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["product"].HasAssociations:
|
||||
|
||||
@@ -18,16 +18,166 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
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": None,
|
||||
"parent": None,
|
||||
"context_identifier": None,
|
||||
"target_view": None,
|
||||
"context_type": context_type,
|
||||
"parent": parent,
|
||||
"context_identifier": context_identifier,
|
||||
"target_view": target_view,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["parent"]:
|
||||
@@ -68,6 +218,6 @@ class Usecase:
|
||||
|
||||
def create_2d_origin(self):
|
||||
self.origin = self.file.createIfcAxis2Placement2D(
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0)),
|
||||
)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
contexts = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.contexts = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
if not file:
|
||||
return
|
||||
cls.contexts = {}
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
subcontexts = {}
|
||||
for subcontext in context.HasSubContexts:
|
||||
subcontexts[int(subcontext.id())] = {
|
||||
"ContextType": subcontext.ContextType,
|
||||
"ContextIdentifier": subcontext.ContextIdentifier,
|
||||
"TargetView": subcontext.TargetView,
|
||||
}
|
||||
cls.contexts[int(context.id())] = {"ContextType": context.ContextType, "HasSubContexts": subcontexts}
|
||||
cls.is_loaded = True
|
||||
@@ -18,11 +18,35 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, context, attributes):
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
|
||||
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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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
|
||||
)
|
||||
|
||||
# Let's fix it!
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"context": context, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -20,11 +20,32 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, context=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.
|
||||
|
||||
:param context: The IfcGeometricRepresentationContext entity to remove
|
||||
:type context: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. 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
|
||||
)
|
||||
|
||||
# Let's just get rid of it completely
|
||||
ifcopenshell.api.run("context.remove_context", model, context=body)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"context": context}
|
||||
|
||||
def execute(self):
|
||||
for subcontext in self.settings["context"].HasSubContexts:
|
||||
|
||||
@@ -21,14 +21,56 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, relating_control=None, related_object=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.
|
||||
|
||||
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
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. 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)
|
||||
|
||||
# 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": None,
|
||||
"related_object": None,
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
|
||||
@@ -21,14 +21,40 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, relating_control=None, related_object=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
|
||||
|
||||
Example:
|
||||
|
||||
.. 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)
|
||||
|
||||
# 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": None,
|
||||
"related_object": None,
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
|
||||
@@ -20,11 +20,38 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_schedule=None, cost_item=None):
|
||||
"""Add a new cost item
|
||||
|
||||
A cost item represents a single line item in a cost schedule. Cost items
|
||||
may then be broken down into cost subitems.
|
||||
|
||||
:param cost_schedule: If the cost item is to be added as a root or top
|
||||
level cost item to a cost schedule, the IfcCostSchedule may be
|
||||
specified. This is mutually exlclusive to the cost_item parameter.
|
||||
:type cost_schedule: ifcopenshell.entity_instance.entity_instance
|
||||
:param cost_item: If the cost item is to be added as a subitem to an
|
||||
existing cost item, the parent IfcCostItem may be specified. This is
|
||||
mutually exclusive to the cost_schedule parameter.
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcCostItem
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# The very first cost item must be in a cost schedule
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
|
||||
# You may add cost items as top level item in the schedule
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# Alternatively you may add them as subitems
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", model, cost_item=item1)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_schedule": None, "cost_item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_schedule": cost_schedule, "cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
cost_item = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCostItem")
|
||||
|
||||
@@ -20,11 +20,60 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, ifc_class="IfcQuantityCount"):
|
||||
"""Adds a new quantity associated with a cost item
|
||||
|
||||
Cost items calculate their subtotal by multiplying the sum of the cost
|
||||
item's "values" by the sum of the cost item's "quantities". The
|
||||
quantities may be either parametrically linked to quantities measured on
|
||||
physical product, or manually specified.
|
||||
|
||||
The quantity must be of a particular type, common examples are:
|
||||
|
||||
- IfcQuantityCount: to count the total occurrences of a product, useful
|
||||
for things like doors, windows, and furniture
|
||||
- IfcQuantityNumber: any other generic numeric quantity
|
||||
- IfcQuantityLength
|
||||
- IfcQuantityArea
|
||||
- IfcQuantityVolume
|
||||
- IfcQuantityWeight
|
||||
- IfcQuantityTime
|
||||
|
||||
A cost item must not mix quantities of different types.
|
||||
|
||||
If an IfcQuantityCount is used, then this API will automatically count
|
||||
all products that this cost item controls (see
|
||||
ifcopenshell.api.controls.assign_control) and prefill that quantity.
|
||||
|
||||
For all other quantity types, the quantity is left as zero and the user
|
||||
must either manually specify the quantity or parametrically link it
|
||||
using another API call.
|
||||
|
||||
:param cost_item: The IfcCostItem to add the quantity to
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param ifc_class: The type of quantity to add
|
||||
:type ifc_class: str, optional
|
||||
:return: The newly created quantity entity, chosen from the ifc_class
|
||||
parameter
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
chair = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurniture")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
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=chair)
|
||||
|
||||
# Let's assume we want to count the amount of chairs to calculate our cost item
|
||||
# Because this is an IfcQuantityCount the count will be automatically set to "1" chair
|
||||
ifcopenshell.api.run("cost.add_cost_item_quantity", model,
|
||||
cost_item=item, ifc_class="IfcQuantityCount")
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "ifc_class": "IfcQuantityCount"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "ifc_class": ifc_class}
|
||||
|
||||
def execute(self):
|
||||
quantity = self.file.create_entity(self.settings["ifc_class"], Name="Unnamed")
|
||||
|
||||
@@ -22,11 +22,39 @@ from datetime import datetime
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, name=None, predefined_type="NOTDEFINED"):
|
||||
"""Add a new cost schedule
|
||||
|
||||
A cost schedule is a group of cost items which typically represent a
|
||||
cost plan or breakdown of the project. This may be used as an estimate,
|
||||
bid, or actual cost.
|
||||
|
||||
Alternatively, a cost schedule may also represent a schedule of rates,
|
||||
which include cost items which capture unit rates for different elements
|
||||
or processes.
|
||||
|
||||
As such, creating a cost schedule is necessary prior to creating and
|
||||
managing any cost items.
|
||||
|
||||
:param name: The name of the cost schedule.
|
||||
:type name: str, optional
|
||||
:param predefined_type: The predefined type of the cost schedule, chosen
|
||||
from a valid type in the IFC documentation for
|
||||
IfcCostScheduleTypeEnum
|
||||
:type predefined_type: str, optional
|
||||
:return: The newly created IfcCostSchedule entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
# Now that we have a cost schedule, we may add cost items to it
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"name": None, "predefined_type": "NOTDEFINED", "start_time": datetime.now()}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"name": name, "predefined_type": predefined_type}
|
||||
|
||||
def execute(self):
|
||||
cost_schedule = ifcopenshell.api.run(
|
||||
|
||||
@@ -18,11 +18,81 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, parent=None):
|
||||
"""Adds a new value or subvalue to a cost item
|
||||
|
||||
A cost item's subtotal can be specified in two ways.
|
||||
|
||||
Option 1 is by simply manually specifying the subtotal value, which
|
||||
represents the full cost of that cost item. This option occurs when a
|
||||
cost item has no quantities associated with it.
|
||||
|
||||
Option 2 is by specifying a unit cost value of the cost item, which is
|
||||
then multiplied by the associated quantity of the cost item, to give us
|
||||
the subtotal. This option occurs when a cost item has quantities
|
||||
associated with it.
|
||||
|
||||
For either option 1 (full cost value) or option 2 (unit cost value), the
|
||||
cost value may be specified as a single number, or as a sum of
|
||||
subcomponents or formulas (e.g. multiplication by wastage factor, or
|
||||
adding taxes or other adjustments).
|
||||
|
||||
This function lets you add a single top level unit value to a cost item,
|
||||
or alternatively price subcomponents by using the "parent" parameter.
|
||||
|
||||
More advanced usage, which involves summing, subcategory-filtered costs,
|
||||
and formulas are possible but not yet documented.
|
||||
|
||||
:param parent: A parent IfcCostItem, if specifying a price directly to a
|
||||
cost item, or a top-level price component. Alternatively, this can
|
||||
be set to a IfcCostValue, if specifying price subcomponents.
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcCostValue
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# We always need a schedule first prior to adding any cost items
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
|
||||
# Option 1: This cost item will have a full cost of 42.0
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item1)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 42.0})
|
||||
|
||||
# Option 2: This cost item will have a unit cost of 5.0 per unit
|
||||
# area, multiplied by the quantity of area specified explicitly as
|
||||
# 3.0, giving us a subtotal cost of 15.0.
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item2)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
quantity = ifcopenshell.api.run("cost.add_cost_item_quantity", model,
|
||||
cost_item=item2, ifc_class="IfcQuantityVolume")
|
||||
ifcopenshell.api.run("cost.edit_cost_item_quantity", model,
|
||||
physical_quantity=quantity, "attributes": {"VolumeValue": 3.0})
|
||||
|
||||
# A cost value may also be specified in terms of the sum of its
|
||||
# subcomponents. In this case, it's broken down into 2 subvalues.
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item1)
|
||||
subvalue1 = ifcopenshell.api.run("cost.add_cost_value", model, parent=value)
|
||||
subvalue2 = ifcopenshell.api.run("cost.add_cost_value", model, parent=value)
|
||||
|
||||
# This specifies that the value is the sum of all subitems
|
||||
# regardless of their cost category. The first subvalue is 2.0 and
|
||||
# the second is 3.0, giving a total value of 5.0.
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value, attributes={"Category": "*"})
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model,
|
||||
cost_value=subvalue1, attributes={"AppliedValue": 2.0})
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model,
|
||||
cost_value=subvalue2, attributes={"AppliedValue": 3.0})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"parent": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"parent": parent}
|
||||
|
||||
def execute(self):
|
||||
value = self.file.create_entity("IfcCostValue")
|
||||
|
||||
@@ -20,11 +20,64 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, products=None, prop_name=""):
|
||||
"""Adds a cost item quantity that is parametrically connected to a product
|
||||
|
||||
A cost item may have its subtotal calculated by multiplying a unit value
|
||||
by a quantity associated with the cost item. That quantity may be either
|
||||
manually specified or parametrically connected to a quantity on a
|
||||
product. This API function lets you create that parametric connection.
|
||||
|
||||
For example, you may wish to have a cost item linked to the "NetVolume"
|
||||
quantity on all IfcSlabs. Each quantity has a name which you can
|
||||
specify. If the quantity is updated in-place (which should occur for
|
||||
Native IFC applications) then the quantity for the cost item will
|
||||
automatically update as well. If the quantity is deleted and then
|
||||
re-added, then the parametric relationship is also lost.
|
||||
|
||||
This API also automatically assigns a control relationship between the
|
||||
cost item and the product, so it is not necessary to use
|
||||
ifcopenshell.api.control.assign_control.
|
||||
|
||||
:param cost_item: The IfcCostItem to assign parametric quantities to
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param products: The IfcObjects to assign parametric quantities to
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param prop_name: The name of the quantity. If this is not specified,
|
||||
then it is assumed that there is no calculated quantity, and the
|
||||
number of objects are counted instead.
|
||||
:type prop_name: str, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# Let's imagine a unit cost of 5.0 per unit volume
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
|
||||
slab = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSlab")
|
||||
# Usually the quantity would be automatically calculated via a
|
||||
# graphical authoring application but let's assign a manual quantity
|
||||
# for now.
|
||||
qto = ifcopenshell.api.run("pset.add_qto", model, product=slab, name="Qto_SlabBaseQuantities")
|
||||
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"NetVolume": 42.0})
|
||||
|
||||
# Now let's parametrically link the slab's quantity to the cost
|
||||
# item. If the slab is edited in the future and 42.0 changes, then
|
||||
# the updated value will also automatically be applied to the cost
|
||||
# item.
|
||||
ifcopenshell.api.run("cost.assign_cost_item_quantity", model,
|
||||
cost_item=item, products=[slab], prop_name="NetVolume")
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "products": [], "prop_name": ""}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "products": products or [], "prop_name": prop_name}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["prop_name"]:
|
||||
|
||||
@@ -20,16 +20,59 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, cost_rate=None):
|
||||
"""Assigns a cost value to a cost item from a schedule of rates
|
||||
|
||||
Instead of assigning cost values from scratch for each cost item in a
|
||||
cost schedule, the cost values may instead be assigned from a schedule
|
||||
of rates.
|
||||
|
||||
A schedule of rates is just another cost schedule which have cost values
|
||||
but no quantities. This API will allow you to "copy" the values from a
|
||||
cost item in the schedule of rates into another cost item in your own
|
||||
cost schedule. When the schedule of rates value is updated, then your
|
||||
cost item values will also be updated. You can think of the schedule of
|
||||
rates as a "template" to quickly populate your rates from.
|
||||
|
||||
:param cost_item: The IfcCostItem that you want to copy the values to
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param cost_rate: The IfcCostItem that you want to copy the values from
|
||||
:type cost_rate: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a schedule of rates with a single rate in it of 5.0
|
||||
rate_tables = ifcopenshell.api.run("cost.add_cost_schedule", model,
|
||||
predefined_type="SCHEDULEOFRATES")
|
||||
rate = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=rate)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
|
||||
# And this schedule will be for our actual cost plan / estimate / etc
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# Now the cost item has the same rate as the one from the schedule of rate's item
|
||||
ifcopenshell.api.run("cost.assign_cost_value", model, cost_item=item, cost_rate=rate)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "cost_rate": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "cost_rate": cost_rate}
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["cost_item"].CostValues:
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item_value", self.file, parent=self.settings["cost_item"], cost_value=cost_value
|
||||
)
|
||||
if self.settings["cost_item"].CostValues:
|
||||
[
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item_value",
|
||||
self.file,
|
||||
parent=self.settings["cost_item"],
|
||||
cost_value=cost_value,
|
||||
)
|
||||
for cost_value in self.settings["cost_item"].CostValues
|
||||
]
|
||||
# This is an assumption, and not part of the official IFC documentation
|
||||
self.settings["cost_item"].CostValues = self.settings["cost_rate"].CostValues
|
||||
|
||||
+63
-4
@@ -21,11 +21,70 @@ import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None):
|
||||
"""Calculates the total cost of all resources associated with a cost item
|
||||
|
||||
A cost item may have construction resources (e.g. equipment, material,
|
||||
etc) assigned to it. Construction resources may be assigned directly to
|
||||
the cost item, or assigned first to a task, and the task is then
|
||||
assigned to the cost item.
|
||||
|
||||
The cost of a resource is calculated by the total sum of all of its base
|
||||
costs. If no quantity is provided, that sum is considered to be the
|
||||
total cost. Otherwise, it is considered to be a unit cost, and is then
|
||||
multiplied by the resource quantity. The quantity is either stored as a
|
||||
base quantity (such as a volume) for a things like material resources,
|
||||
or as a duration as a daily rate for labour resources.
|
||||
|
||||
The final calculated cost is set as the cost item's value. Any
|
||||
previously existing values are removed.
|
||||
|
||||
:param cost_item: The IfcCostItem to calculate
|
||||
:type cost_item: ifccopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# First, we need a cost schedule and item
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# Let's imagine we have our own formworking crew
|
||||
crew = ifcopenshell.api.run("resource.add_resource", model, ifc_class="IfcCrewResource")
|
||||
|
||||
# ... and they need concrete
|
||||
concrete = ifcopenshell.api.run("resource.add_resource", model,
|
||||
ifc_class="IfcConstructionMaterialResource", parent_resource=crew)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=item, related_object=concrete)
|
||||
# ... which has a unit price of 42.0 per m3
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=concrete)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 42.0})
|
||||
# ... and a volume of 200m3
|
||||
quantity = ifcopenshell.api.run("resource.add_resource_quantity", model,
|
||||
resource=concrete, ifc_class="IfcQuantityVolume")
|
||||
ifcopenshell.api.run("resource.edit_resource_quantity", model,
|
||||
physical_quantity=quantity, "attributes": {"VolumeValue": 200.0})
|
||||
|
||||
# Let's say they also need some equipment
|
||||
equipment = ifcopenshell.api.run("resource.add_resource", model,
|
||||
ifc_class="IfcConstructionEquipmentResource", parent_resource=crew)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=item, related_object=equipment)
|
||||
# ... with a fixed price of 50,000
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=concrete)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 42.0})
|
||||
|
||||
# (42 * 200) + 50000 = 58400 is our calculated cost
|
||||
ifcopenshell.api.run("cost.calculate_cost_item_resource_value", model, cost_item=item)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["cost_item"].CostValues or []:
|
||||
|
||||
@@ -20,11 +20,39 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, source=None, destination=None):
|
||||
"""Copies all cost values from one cost item to another
|
||||
|
||||
Any previously existing values will be removed. The entire value is
|
||||
copied, including all components and formulas. However they are not
|
||||
parametrically linked, so if one value changes, the other will not.
|
||||
|
||||
:param source: The IfcCostItem to copy cost values from
|
||||
:type source: ifcopenshell.entity_instance.entity_instance
|
||||
:param destination: The IfcCostItem to copy cost values from
|
||||
:type destination: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Assume we have a schedule with multiple items in it
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# One of the items has a value
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5000.0})
|
||||
|
||||
# Let's copy the value from one item to another
|
||||
ifcopenshell.api.run("cost.copy_cost_item_values", model, source=item1, destination=item2)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"source": None, "destination": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"source": source, "destination": destination}
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["destination"].CostValues or []:
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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.util.date
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.cost
|
||||
|
||||
|
||||
class CostValueTrait:
|
||||
@classmethod
|
||||
def load_cost_values(cls, root_element, data):
|
||||
data["CostValues"] = []
|
||||
data["CategoryValues"] = {}
|
||||
data["UnitBasisValueComponent"] = None
|
||||
data["UnitBasisUnitSymbol"] = None
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
values = root_element.BaseCosts
|
||||
for cost_value in values or []:
|
||||
cls.load_cost_value(root_element, data, cost_value)
|
||||
data["CostValues"].append(cost_value.id())
|
||||
data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"]
|
||||
if cost_value.UnitBasis:
|
||||
cost_value_data = cls.cost_values[cost_value.id()]
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
if data["UnitBasisValueComponent"]:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
|
||||
|
||||
@classmethod
|
||||
def load_cost_value(cls, root_element, root_element_data, cost_value):
|
||||
value_data = cost_value.get_info()
|
||||
del value_data["AppliedValue"]
|
||||
if value_data["UnitBasis"]:
|
||||
data = cost_value.UnitBasis.get_info()
|
||||
data["ValueComponent"] = data["ValueComponent"].wrappedValue
|
||||
data["UnitComponent"] = data["UnitComponent"].id()
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent)
|
||||
value_data["UnitBasis"] = data
|
||||
if value_data["ApplicableDate"]:
|
||||
value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
|
||||
if value_data["FixedUntilDate"]:
|
||||
value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
|
||||
value_data["Components"] = [c.id() for c in value_data["Components"] or []]
|
||||
value_data["AppliedValue"] = cls.calculate_applied_value(root_element, cost_value)
|
||||
|
||||
if cost_value.Category not in [None, "*"]:
|
||||
root_element_data["CategoryValues"].setdefault(cost_value.Category, 0)
|
||||
root_element_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"]
|
||||
|
||||
value_data["Formula"] = ifcopenshell.util.cost.serialise_cost_value(cost_value)
|
||||
|
||||
cls.cost_values[cost_value.id()] = value_data
|
||||
for component in cost_value.Components or []:
|
||||
cls.load_cost_value(root_element, root_element_data, component)
|
||||
|
||||
@classmethod
|
||||
def calculate_applied_value(cls, root_element, cost_value, category_filter=None):
|
||||
if cost_value.ArithmeticOperator and cost_value.Components:
|
||||
component_values = []
|
||||
for component in cost_value.Components:
|
||||
component_values.append(cls.calculate_applied_value(root_element, component, category_filter))
|
||||
if cost_value.ArithmeticOperator == "ADD":
|
||||
return sum(component_values)
|
||||
result = component_values.pop(0)
|
||||
if cost_value.ArithmeticOperator == "DIVIDE":
|
||||
for value in component_values:
|
||||
try:
|
||||
result /= value
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
elif cost_value.ArithmeticOperator == "MULTIPLY":
|
||||
for value in component_values:
|
||||
result *= value
|
||||
elif cost_value.ArithmeticOperator == "SUBTRACT":
|
||||
for value in component_values:
|
||||
result -= value
|
||||
return result
|
||||
if cost_value.Category is None:
|
||||
return cls.get_primitive_applied_value(cost_value.AppliedValue)
|
||||
elif cost_value.Category == "*":
|
||||
if root_element.IsNestedBy:
|
||||
return cls.sum_child_root_elements(root_element)
|
||||
else:
|
||||
return cls.get_primitive_applied_value(cost_value.AppliedValue)
|
||||
elif cost_value.Category:
|
||||
if root_element.IsNestedBy:
|
||||
return cls.sum_child_root_elements(root_element, category_filter=cost_value.Category)
|
||||
else:
|
||||
return cls.get_primitive_applied_value(cost_value.AppliedValue)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def sum_child_root_elements(cls, root_element, category_filter=None):
|
||||
result = 0
|
||||
for rel in root_element.IsNestedBy:
|
||||
for child_root_element in rel.RelatedObjects:
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = child_root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
values = child_root_element.BaseCosts
|
||||
for child_cost_value in values or []:
|
||||
if category_filter and child_cost_value.Category != category_filter:
|
||||
continue
|
||||
child_applied_value = cls.calculate_applied_value(child_root_element, child_cost_value)
|
||||
child_quantity = cls.get_total_quantity(child_root_element)
|
||||
if child_cost_value.UnitBasis:
|
||||
value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue
|
||||
result += child_quantity / value_component * child_applied_value
|
||||
else:
|
||||
result += child_quantity * child_applied_value
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_total_quantity(cls, root_element):
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
return sum([q[3] for q in root_element.CostQuantities or []]) or 1.0
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
return root_element.BaseQuantity[3] if root_element.BaseQuantity else 1.0
|
||||
|
||||
@classmethod
|
||||
def get_primitive_applied_value(cls, applied_value):
|
||||
if not applied_value:
|
||||
return 0.0
|
||||
elif isinstance(applied_value, float):
|
||||
return applied_value
|
||||
elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float):
|
||||
return applied_value.wrappedValue
|
||||
elif applied_value.is_a("IfcMeasureWithUnit"):
|
||||
return applied_value.ValueComponent
|
||||
assert False, "Applied value {applied_value} not implemented"
|
||||
|
||||
|
||||
class Data(CostValueTrait):
|
||||
is_loaded = False
|
||||
cost_schedules = {}
|
||||
cost_items = {}
|
||||
physical_quantities = {}
|
||||
cost_values = {}
|
||||
categories = []
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.cost_schedules = {}
|
||||
cls.cost_items = {}
|
||||
cls.physical_quantities = {}
|
||||
cls.cost_values = {}
|
||||
cls.categories = []
|
||||
|
||||
@classmethod
|
||||
def set_categories(cls, categories):
|
||||
cls.categories = categories
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
cls.file = file
|
||||
cls.cost_schedules = {}
|
||||
cls.cost_items = {}
|
||||
cls.physical_quantities = {}
|
||||
cls.cost_values = {}
|
||||
|
||||
for cost_schedule in cls.file.by_type("IfcCostSchedule"):
|
||||
data = cost_schedule.get_info()
|
||||
del data["OwnerHistory"]
|
||||
if data["SubmittedOn"]:
|
||||
data["SubmittedOn"] = ifcopenshell.util.date.ifc2datetime(data["SubmittedOn"])
|
||||
if data["UpdateDate"]:
|
||||
data["UpdateDate"] = ifcopenshell.util.date.ifc2datetime(data["UpdateDate"])
|
||||
data["Controls"] = []
|
||||
for rel in cost_schedule.Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcCostItem"):
|
||||
data["Controls"].append(related_object.id())
|
||||
break # We are only allowed one summary cost item
|
||||
cls.cost_schedules[cost_schedule.id()] = data
|
||||
|
||||
for cost_item in cls.file.by_type("IfcCostItem"):
|
||||
data = cost_item.get_info()
|
||||
del data["OwnerHistory"]
|
||||
del data["CostValues"]
|
||||
data["IsNestedBy"] = []
|
||||
data["Controls"] = {}
|
||||
for rel in cost_item.IsNestedBy:
|
||||
[data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
|
||||
parametric_quantities = []
|
||||
for rel in cost_item.Controls:
|
||||
for related_object in rel.RelatedObjects or []:
|
||||
quantities = cls.get_object_quantities(cost_item, related_object)
|
||||
data["Controls"][related_object.id()] = quantities
|
||||
parametric_quantities.extend(quantities)
|
||||
cls.cost_items[cost_item.id()] = data
|
||||
cls.load_cost_item_quantities(cost_item, data, parametric_quantities)
|
||||
cls.load_cost_values(cost_item, data)
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def get_object_quantities(cls, cost_item, element):
|
||||
if not element.is_a("IfcObject"):
|
||||
return []
|
||||
results = []
|
||||
for relationship in element.IsDefinedBy:
|
||||
if not relationship.is_a("IfcRelDefinesByProperties"):
|
||||
continue
|
||||
qto = relationship.RelatingPropertyDefinition
|
||||
if not qto.is_a("IfcElementQuantity"):
|
||||
continue
|
||||
for prop in qto.Quantities:
|
||||
if prop in cost_item.CostQuantities or []:
|
||||
results.append(prop.id())
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_quantities(cls, cost_item, data, parametric_quantities):
|
||||
data["CostQuantities"] = []
|
||||
data["TotalCostQuantity"] = cls.get_total_quantity(cost_item)
|
||||
for quantity in cost_item.CostQuantities or []:
|
||||
if quantity.id() in parametric_quantities:
|
||||
continue
|
||||
quantity_data = quantity.get_info()
|
||||
del quantity_data["Unit"]
|
||||
cls.physical_quantities[quantity.id()] = quantity_data
|
||||
data["CostQuantities"].append(quantity.id())
|
||||
data["Unit"] = None
|
||||
data["UnitSymbol"] = "?"
|
||||
if cost_item.CostQuantities:
|
||||
quantity = cost_item.CostQuantities[0]
|
||||
unit = ifcopenshell.util.unit.get_property_unit(quantity, cls.file)
|
||||
if unit:
|
||||
data["Unit"] = unit.id()
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
else:
|
||||
data["Unit"] = None
|
||||
data["UnitSymbol"] = None
|
||||
@@ -18,11 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostItem
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostItem, consult the IFC documentation.
|
||||
|
||||
:param cost_item: The IfcCostItem entity you want to edit
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
ifcopenshell.api.run("cost.edit_cost_item", model, cost_item=item, attributes={"Name": "Foo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,37 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, physical_quantity=None, attributes=None):
|
||||
"""Edits the attributes of an IfcPhysicalQuantity
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcPhysicalQuantity, consult the IFC documentation.
|
||||
|
||||
:param physical_quantity: The IfcPhysicalQuantity entity you want to edit
|
||||
:type physical_quantity: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# This cost item will have a unit cost of 5 and a volume of 3
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
quantity = ifcopenshell.api.run("cost.add_cost_item_quantity", model,
|
||||
cost_item=item, ifc_class="IfcQuantityVolume")
|
||||
ifcopenshell.api.run("cost.edit_cost_item_quantity", model,
|
||||
physical_quantity=quantity, "attributes": {"VolumeValue": 3.0})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"physical_quantity": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"physical_quantity": physical_quantity, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,30 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_schedule=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostSchedule
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostSchedule, consult the IFC documentation.
|
||||
|
||||
:param cost_schedule: The IfcCostSchedule entity you want to edit
|
||||
:type cost_schedule: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
ifcopenshell.api.run("cost.edit_cost_schedule", model,
|
||||
cost_schedule=schedule, attributes={"Name": "Foo"})
|
||||
"""
|
||||
|
||||
self.file = file
|
||||
self.settings = {"cost_schedule": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_schedule": cost_schedule, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -22,11 +22,33 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_value=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostValue
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostValue, consult the IFC documentation.
|
||||
|
||||
:param cost_value: The IfcCostValue entity you want to edit
|
||||
:type cost_value: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# This cost item will have a total cost of 42
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 42.0})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_value": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_value": cost_value, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -23,11 +23,35 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_value=None, formula=None):
|
||||
"""Sets a cost value based on a formula, similar to formulas in spreadsheets
|
||||
|
||||
Costs may be made up of many components (e.g. labour, material, waste
|
||||
factor, taxes, etc). This can be easily represented in the form of a
|
||||
formula similar thta would be used in spreadsheet applications.
|
||||
|
||||
For more information, see ifcopenshell.util.cost
|
||||
|
||||
:param cost_value: The IfcCostValue to set the values of
|
||||
:type cost_value: ifcopenshell.entity_instance.entity_instance
|
||||
:param formula: The formula following the language of ifcopenshell.util.cost
|
||||
:type formula: str
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value_formula", model, cost_value=value,
|
||||
formula="5000 * 1.19")
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_value": None, "formula": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_value": cost_value, "formula": formula or {}}
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
|
||||
@@ -20,11 +20,28 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None):
|
||||
"""Removes a cost item
|
||||
|
||||
All associated relationships with the cost item are also removed,
|
||||
however the related resources, products, and tasks themselves are
|
||||
retained.
|
||||
|
||||
:param cost_item: The IfcCostItem entity you want to remove
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
ifcopenshell.api.run("cost.remove_cost_item", model, cost_item=item)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
|
||||
@@ -18,11 +18,34 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, physical_quantity=None):
|
||||
"""Removes a quantity assigned to a cost item
|
||||
|
||||
If the quantity is part of a product (e.g. wall), then the quantity will
|
||||
still exist and merely the relationship to the cost item will be
|
||||
removed.
|
||||
|
||||
:param cost_item: The IfcCostItem that the quantity is assigned to
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param physical_quantity: The IfcPhysicalQuantity to remove
|
||||
:type physical_quantity: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
quantity = ifcopenshell.api.run("cost.add_cost_item_quantity", model,
|
||||
cost_item=item, ifc_class="IfcQuantityVolume")
|
||||
# Let's change our mind and delete it
|
||||
ifcopenshell.api.run("cost.remove_cost_item", model,
|
||||
cost_item=item, physical_quantity=quantity)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "physical_quantity": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "physical_quantity": physical_quantity}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["physical_quantity"])) == 1:
|
||||
|
||||
@@ -18,11 +18,27 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_schedule=None):
|
||||
"""Removes a cost schedule
|
||||
|
||||
All associated relationships with the cost schedule are also removed,
|
||||
including all cost items.
|
||||
|
||||
:param cost_schedule: The IfcCostSchedule entity you want to remove
|
||||
:type cost_schedule: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
ifcopenshell.api.run("cost.remove_cost_schedule", model, cost_schedule=schedule)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_schedule": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_schedule": cost_schedule}
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
|
||||
@@ -18,11 +18,36 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, parent=None, cost_value=None):
|
||||
"""Removes a cost value
|
||||
|
||||
The cost value may be assigned either to a cost item, a construction
|
||||
resource, or another cost value (i.e. it is a subcomponent of a cost)
|
||||
|
||||
:param parent: The IfcCostItem, IfcConstructionResource, or IfcCostValue
|
||||
that the IfcCostValue is assigned to.
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance
|
||||
:param cost_value: The IfcCostValue that you want to remove
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# This cost item will have a unit cost of 5 and a volume of 3
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
|
||||
ifcopenshell.api.run("cost.remove_cost_value", model, parent=item, cost_value=value)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"parent": None, "cost_value": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"parent": parent, "cost_value": cost_value}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["cost_value"])) == 1:
|
||||
|
||||
@@ -20,11 +20,54 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, cost_item=None, products=None):
|
||||
"""Removes quantities of a cost item that are calculated on products
|
||||
|
||||
A cost item may have quantities that are parametrically calculated on
|
||||
physical products. This lets you remove those quantities. This means
|
||||
that any future changes in the physical product's dimensions will not
|
||||
have any impact on the cost item.
|
||||
|
||||
:param cost_item: The IfcCostItem to remove quantities from
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:param products: A list of IfcProducts that may have parametrically
|
||||
connected quantities to the cost item
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
# Let's imagine a unit cost of 5.0 per unit volume
|
||||
value = ifcopenshell.api.run("cost.add_cost_value", model, parent=item)
|
||||
ifcopenshell.api.run("cost.edit_cost_value", model, cost_value=value,
|
||||
attributes={"AppliedValue": 5.0})
|
||||
|
||||
slab = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSlab")
|
||||
# Usually the quantity would be automatically calculated via a
|
||||
# graphical authoring application but let's assign a manual quantity
|
||||
# for now.
|
||||
qto = ifcopenshell.api.run("pset.add_qto", model, product=slab, name="Qto_SlabBaseQuantities")
|
||||
ifcopenshell.api.run("pset.edit_qto", model, qto=qto, properties={"NetVolume": 42.0})
|
||||
|
||||
# Now let's parametrically link the slab's quantity to the cost
|
||||
# item. If the slab is edited in the future and 42.0 changes, then
|
||||
# the updated value will also automatically be applied to the cost
|
||||
# item.
|
||||
ifcopenshell.api.run("cost.assign_cost_item_quantity", model,
|
||||
cost_item=item, products=[slab], prop_name="NetVolume")
|
||||
|
||||
# Let's change our mind and remove the parametric connection
|
||||
ifcopenshell.api.run("cost.unassign_cost_item_quantity", model,
|
||||
cost_item=item, products=[slab])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "products": []}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"cost_item": cost_item, "products": products or []}
|
||||
|
||||
def execute(self):
|
||||
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
|
||||
|
||||
@@ -20,11 +20,41 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, parent=None):
|
||||
"""Adds a new document information to the project
|
||||
|
||||
An IFC document information is a document associated with the project.
|
||||
It may be a drawing, specification, schedule, certificate, warranty
|
||||
guarantee, manual, contract, and so on. They are often used for drawings
|
||||
and facility management purposes.
|
||||
|
||||
A document may also be a subdocument of a larger document, this is
|
||||
useful for superseding documents or tracking older versions. The parent
|
||||
is considered the latest version and the children are older revisions.
|
||||
|
||||
:param parent: The parent document, if necessary.
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance, optional
|
||||
:return: The newly created IfcDocumentInformation entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
# A document typically has a unique drawing or document name (which
|
||||
# follows a coding system depending on the project), as well as a
|
||||
# title. This should match what is shown on the titleblock or title
|
||||
# page of the document. At a minimum you'd also want to specify a
|
||||
# URI location. The location may be on local, or on a CDE, or any
|
||||
# other platform.
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"parent": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"parent": parent}
|
||||
|
||||
def execute(self):
|
||||
id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification"
|
||||
|
||||
@@ -18,11 +18,52 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, information=None):
|
||||
"""Creates a new reference to a document to assign to products
|
||||
|
||||
A document may be associated with physical products, tasks, cost items,
|
||||
and so on. For example, spaces, storeys, and buildings may have a list
|
||||
of associated drawings so you can see which drawings (e.g. plans,
|
||||
sections, details) are documenting that location. Alternatively,
|
||||
equipment may have associated training manuals, operation and
|
||||
maintenance manuals or detailed assembly drawings. Resources may be
|
||||
training certification required, schedules may have gantt charts or bid
|
||||
documents, and so on.
|
||||
|
||||
In order to associate a document with an object, a reference to that
|
||||
document needs to be created. It could be a reference to the entire
|
||||
document, or a reference to a particular page or chapter. See
|
||||
ifcopenshell.api.document.assign_document for more information.
|
||||
|
||||
:param information: The IfcDocumentInformation that the reference will
|
||||
be created for
|
||||
:type information: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcDocumentReference entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
|
||||
# In this case, we don't specify any more information, and so the
|
||||
# reference is for the entire document, as opposed to a single page or
|
||||
# chapter or section.
|
||||
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
|
||||
# Alternatively, we can specify a single section, such as by a
|
||||
# subheading code.
|
||||
reference2 = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
ifcopenshell.api.run("document.edit_reference", model,
|
||||
reference=reference2, attributes={"Identification": "2.1.15"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"information": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"information": information}
|
||||
|
||||
def execute(self):
|
||||
if self.file.schema == "IFC2X3":
|
||||
|
||||
@@ -20,14 +20,47 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, document=None):
|
||||
"""Assigns a document to a product
|
||||
|
||||
An object may be assigned to zero, one, or multiple documents. Almost
|
||||
any object or property may be assigned to a document, though typically
|
||||
we'd only use it for spaces, types, physical products and schedules.
|
||||
Adding a new assignment is typically done using a document reference and
|
||||
an object. IFC technically allows association with a document
|
||||
information and an object, but this is not encouraged because it is not
|
||||
consistent with other external relationships (such as classification
|
||||
systems or libraries).
|
||||
|
||||
:param product: The object to associate the document to. This could be
|
||||
almost any sensible object in IFC.
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param document: The IfcDocumentReference to associate to, or
|
||||
alternatively an IfcDocumentInformation, though this is not
|
||||
recommended.
|
||||
:type document: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAssociatesDocument relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
|
||||
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
|
||||
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"document": None,
|
||||
"product": product,
|
||||
"document": document,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
rel = self.get_document_rel()
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
products = {}
|
||||
references = {}
|
||||
information = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.products = {}
|
||||
cls.references = {}
|
||||
cls.information = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id=None):
|
||||
cls._file = file
|
||||
if not cls._file:
|
||||
return
|
||||
if product_id:
|
||||
return cls.load_product(product_id)
|
||||
cls.load_references()
|
||||
cls.load_information()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def load_product(cls, product_id):
|
||||
product = cls._file.by_id(product_id)
|
||||
cls.products[product_id] = []
|
||||
if not product.HasAssociations:
|
||||
return
|
||||
for association in product.HasAssociations:
|
||||
if association.is_a("IfcRelAssociatesDocument"):
|
||||
cls.products[product_id].append(association.RelatingDocument.id())
|
||||
|
||||
@classmethod
|
||||
def load_information(cls):
|
||||
cls.information = {}
|
||||
for information in cls._file.by_type("IfcDocumentInformation"):
|
||||
data = information.get_info()
|
||||
if cls._file.schema == "IFC2X3":
|
||||
for attribute in ["CreationTime", "LastRevisionTime", "ValidFrom", "ValidUntil"]:
|
||||
if data[attribute]:
|
||||
data[attribute] = ifcopenshell.util.date.ifc2datetime(data[attribute]).isoformat()
|
||||
if data["ElectronicFormat"]:
|
||||
data["ElectronicFormat"] = "{}/{}".format(
|
||||
information.ElectronicFormat.MimeContentType, information.ElectronicFormat.MimeSubtype
|
||||
)
|
||||
cls.information[information.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_references(cls):
|
||||
cls.references = {}
|
||||
for reference in cls._file.by_type("IfcDocumentReference"):
|
||||
data = reference.get_info()
|
||||
if cls._file.schema == "IFC2X3":
|
||||
if reference.ReferenceToDocument:
|
||||
data["ReferencedDocument"] = reference.ReferenceToDocument[0].id()
|
||||
elif reference.ReferencedDocument:
|
||||
data["ReferencedDocument"] = reference.ReferencedDocument.id()
|
||||
cls.references[reference.id()] = data
|
||||
@@ -18,11 +18,31 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, information=None, attributes=None):
|
||||
"""Edits the attributes of an IfcDocumentInformation
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcDocumentInformation, consult the IFC documentation.
|
||||
|
||||
:param reference: The IfcDocumentInformation 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
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"information": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"information": information, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -18,11 +18,34 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, reference=None, attributes=None):
|
||||
"""Edits the attributes of an IfcDocumentReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcDocumentReference, consult the IFC documentation.
|
||||
|
||||
:param reference: The IfcDocumentReference 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
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
ifcopenshell.api.run("document.edit_reference", model,
|
||||
reference=reference, attributes={"Identification": "2.1.15"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"reference": reference, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -21,11 +21,27 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, information=None):
|
||||
"""Removes a document information
|
||||
|
||||
All references and associations are also removed.
|
||||
|
||||
:param information: The IfcDocumentInformation to remove
|
||||
:type information: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Add a document
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
# ... and remove it!
|
||||
ifcopenshell.api.run("document.remove_information", model, information=document)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"information": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"information": information}
|
||||
|
||||
def execute(self):
|
||||
for reference in self.settings["information"].HasDocumentReferences or []:
|
||||
|
||||
@@ -18,11 +18,26 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, reference=None):
|
||||
"""Remove a document reference
|
||||
|
||||
All associations with objects are removed.
|
||||
|
||||
:param reference: The IfcDocumentReference to remove
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
ifcopenshell.api.run("document.remove_reference", model, reference=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"reference": reference}
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["reference"].DocumentRefForObjects or []:
|
||||
|
||||
@@ -20,14 +20,40 @@ import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, document=None):
|
||||
"""Unassigns a document and a product association
|
||||
|
||||
:param product: The object that the document reference or information is
|
||||
related to.
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param document: The IfcDocumentReference (typically) or in rare cases
|
||||
the IfcDocumentInformation that is associated with the product
|
||||
:type document: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
ifcopenshell.api.run("document.edit_information", model,
|
||||
information=document,
|
||||
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
|
||||
"Location": "A-GA-6100 - Overall Plan.pdf"})
|
||||
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
|
||||
|
||||
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
|
||||
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference)
|
||||
|
||||
# Now let's change our mind and remove the association
|
||||
ifcopenshell.api.run("document.unassign_document", model, product=storey, document=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"document": None,
|
||||
"product": product,
|
||||
"document": document,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["product"].HasAssociations:
|
||||
|
||||
@@ -21,14 +21,48 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, relating_product=None, related_object=None):
|
||||
"""Associates a product and an object, typically for annotation
|
||||
|
||||
Warning: this is an experimental API.
|
||||
|
||||
When you want to draw attention to a feature or characteristic (such as
|
||||
a dimension, material, or name) or of a product (e.g. wall, slab,
|
||||
furniture, etc), an annotation object is created. This annotation is
|
||||
then associated with the product so that it can reference attributes,
|
||||
properties, and relationships.
|
||||
|
||||
For example, an annotation of a line will be associated with a grid
|
||||
axis, such that when that grid axis moves, the annotation of that grid
|
||||
axis (which is typically truncated to the extents of a drawing) will
|
||||
also move.
|
||||
|
||||
Another example might be a label of a furniture product, which might
|
||||
have some text of the name of the furniture to be shown on drawings or
|
||||
in 3D.
|
||||
|
||||
:param relating_product: The IfcProduct the object is related to
|
||||
:type relating_product: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The object (typically IfcAnnotation) that the
|
||||
product is related to
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The created IfcRelAssignsToProduct relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
furniture = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurniture")
|
||||
annotation = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcAnnotation")
|
||||
ifcopenshell.api.run("drawing.assign_product", model,
|
||||
relating_product=furniture, related_object=annotation)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_product": None,
|
||||
"related_object": None,
|
||||
"relating_product": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
is_grid_axis = self.settings["relating_product"].is_a("IfcGridAxis")
|
||||
|
||||
@@ -18,11 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, text_literal=None, attributes=None):
|
||||
"""Edits the attributes of an IfcTextLiteral
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcTextLiteral, consult the IFC documentation.
|
||||
|
||||
:param reference: The IfcTextLiteral 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
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
text = model.createIfcTextLiteral()
|
||||
ifcopenshell.api.run("drawing.edit_text_literal", model,
|
||||
text_literal=text, attributes={"Literal": "MY ANNOTATION"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"text_literal": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"text_literal": text_literal, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -21,14 +21,40 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, relating_product=None, related_object=None):
|
||||
"""Unassigns a product and an object (typically an annotation)
|
||||
|
||||
Smart annotation objects can be associated with products so that they
|
||||
can annotate attributes and properties. This function lets you remove
|
||||
the association, so that you may change the assocation with another
|
||||
object later or leave the annotation as a "dumb" annotation.
|
||||
|
||||
:param relating_product: The IfcProduct the object is related to
|
||||
:type relating_product: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The object (typically IfcAnnotation) that the
|
||||
product is related to
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The created IfcRelAssignsToProduct relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
furniture = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurniture")
|
||||
annotation = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcAnnotation")
|
||||
ifcopenshell.api.run("drawing.assign_product", model,
|
||||
relating_product=furniture, related_object=annotation)
|
||||
|
||||
# Let's change our mind and remove the relationship
|
||||
ifcopenshell.api.run("drawing.unassign_product", model,
|
||||
relating_product=furniture, related_object=annotation)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_product": None,
|
||||
"related_object": None,
|
||||
"relating_product": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
|
||||
@@ -20,14 +20,59 @@ import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, context=None, axis=None):
|
||||
"""Adds a new axis representation
|
||||
|
||||
Certain objects are typically "axis-based", such as walls, beams,
|
||||
and columns. This means you can represent them abstractly by simply
|
||||
drawing a single line either in 2D (such as for walls) or 3D (for beams
|
||||
and columns). Humans can understand this axis-based representation as
|
||||
being a simplification of a layered extrusion or a profile that is being
|
||||
extruded along that axis and joined to other elements.
|
||||
|
||||
Using an axis-based representation makes it easy for users and computers
|
||||
to analyse connectivity and spatial relationships, as well as makes it
|
||||
easy to parametrically edit these objects by simply stretching the start
|
||||
or end of the axis.
|
||||
|
||||
For now, only simple straight line axes are supported, represented by a
|
||||
start and end coordinate. The order is important. For walls, the start
|
||||
must be at the minimum local X ordinate, and the end at the maximum
|
||||
local X ordinate. For beams and columns, the start is at the minimum
|
||||
local Z ordinate, and the end of the maximum local Z ordinate. The first
|
||||
coordinate is the "start" and the second coordinate is the "end". This
|
||||
stat and end is then used to determine any parametric junctions with
|
||||
other elements.
|
||||
|
||||
Using an axis-representation is optional, but highly recommended for
|
||||
"standard" representations of walls, beams, columns, and other
|
||||
structural members. A rule of thumb is that if you can draw it as a line
|
||||
on paper, you can probably represent it using an axis.
|
||||
|
||||
:param context: The IfcGeometricRepresentationContext that the
|
||||
representation is part of. This must be either a
|
||||
Model/Axis/GRAPH_VIEW (3D) or Plan/Axis/GRAPH_VIEW (2D).
|
||||
:type context: ifcopenshell.entity_instance.entity_instance
|
||||
:param axis: The axis, as a list of two coordinates, the coordinates
|
||||
being either a list of 2 or 3 float coordinates depending on whether
|
||||
the axis is 2D or 3D.
|
||||
:type axis: list[list[float]]
|
||||
:return: The newly created IfcShapeRepresentation entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
context = ifcopenshell.util.representation.get_context(model, "Plan", "Axis", "GRAPH_VIEW")
|
||||
axis = ifcopenshell.api.run("geometry.add_axis_representation", model,
|
||||
context=context, axis=[(0.0, 0.0), (1.0, 0.0)])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"axis": [], # A list of ordered coordinates for the axis
|
||||
"context": context,
|
||||
"axis": axis or [],
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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.util.unit
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, V
|
||||
from ifcopenshell.api.geometry.add_window_representation import create_ifc_window
|
||||
from mathutils import Vector
|
||||
|
||||
import collections
|
||||
|
||||
|
||||
SUPPORTED_DOOR_TYPES = (
|
||||
"SINGLE_SWING_LEFT",
|
||||
"SINGLE_SWING_RIGHT",
|
||||
"DOUBLE_SWING_RIGHT",
|
||||
"DOUBLE_SWING_LEFT",
|
||||
"DOUBLE_DOOR_SINGLE_SWING",
|
||||
"DOUBLE_DOOR_DOUBLE_SWING",
|
||||
)
|
||||
|
||||
|
||||
def create_ifc_door_lining(
|
||||
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
|
||||
):
|
||||
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
|
||||
|
||||
`thickness` can be also defined just as 1 float value.
|
||||
"""
|
||||
if not isinstance(thickness, collections.abc.Iterable):
|
||||
thickness = [thickness] * 2
|
||||
|
||||
th_side, th_up = thickness
|
||||
|
||||
points = [
|
||||
V(0.0, 0.0, 0.0),
|
||||
V(0.0, 0.0, size.z),
|
||||
V(size.x, 0.0, size.z),
|
||||
V(size.x, 0.0, 0.0),
|
||||
V(size.x - th_side, 0.0, 0.0),
|
||||
V(size.x - th_side, 0.0, size.z - th_up),
|
||||
V(th_side, 0.0, size.z - th_up),
|
||||
V(th_side, 0.0, 0.0),
|
||||
]
|
||||
|
||||
door_lining = builder.polyline(points, closed=True)
|
||||
door_lining = builder.extrude(door_lining, size.y, extrusion_vector=V(0, 1, 0))
|
||||
builder.translate(door_lining, position)
|
||||
|
||||
return door_lining
|
||||
|
||||
|
||||
def create_ifc_box(builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()):
|
||||
rect = builder.rectangle(size.xy)
|
||||
box = builder.extrude(rect, size.z, position=position, extrusion_vector=V(0, 0, 1))
|
||||
return box
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
"""units in settings expected to be in ifc project units"""
|
||||
self.file = file
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoor.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorLiningProperties.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorPanelProperties.htm
|
||||
self.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(self.file)}
|
||||
self.settings.update(
|
||||
{
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"overall_height": self.convert_si_to_unit(2.0),
|
||||
"overall_width": self.convert_si_to_unit(0.9),
|
||||
# DOUBLE_DOOR_DOUBLE_SWING, DOUBLE_DOOR_FOLDING, DOUBLE_DOOR_LIFTING_VERTICAL,
|
||||
# DOUBLE_DOOR_SINGLE_SWING, DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_LEFT,
|
||||
# DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_RIGHT, DOUBLE_DOOR_SLIDING,
|
||||
# DOUBLE_SWING_LEFT, DOUBLE_SWING_RIGHT, FOLDING_TO_LEFT,
|
||||
# FOLDING_TO_RIGHT, LIFTING_HORIZONTAL, LIFTING_VERTICAL_LEFT,
|
||||
# LIFTING_VERTICAL_RIGHT, REVOLVING, REVOLVING_VERTICAL,
|
||||
# ROLLINGUP, SINGLE_SWING_LEFT, SINGLE_SWING_RIGHT, SLIDING_TO_LEFT,
|
||||
# SLIDING_TO_RIGHT, SWING_FIXED_LEFT, SWING_FIXED_RIGHT
|
||||
"operation_type": "SINGLE_SWING_LEFT", # door type
|
||||
"lining_properties": {
|
||||
"LiningDepth": self.convert_si_to_unit(0.050),
|
||||
"LiningThickness": self.convert_si_to_unit(0.050),
|
||||
# offset from the outer side of the wall (by Y-axis)
|
||||
"LiningOffset": self.convert_si_to_unit(0.0),
|
||||
# offset from the wall
|
||||
"LiningToPanelOffsetX": self.convert_si_to_unit(0.025),
|
||||
# offset from the X-axis (unlike windows)
|
||||
"LiningToPanelOffsetY": self.convert_si_to_unit(0.025),
|
||||
# transom - vertical distance between door and window panels
|
||||
"TransomThickness": self.convert_si_to_unit(0.000),
|
||||
# TransomOffset - distance from the bottom door opening
|
||||
# to the beginning of the transom
|
||||
# unlike windows TransomOffset which goes to the center of the transom
|
||||
"TransomOffset": self.convert_si_to_unit(1.525),
|
||||
"ShapeAspectStyle": None, # DEPRECATED
|
||||
# Casing cover wall faces around the opening
|
||||
# on the left, right and upper sides
|
||||
# Casing should be either on both sides of the wall or no casing
|
||||
# If `LiningOffset` is present then therefore casing is not possible on outer wall
|
||||
# therefore there will be no casing on inner wall either
|
||||
"CasingDepth": self.convert_si_to_unit(0.005),
|
||||
"CasingThickness": self.convert_si_to_unit(0.075), # by Z-axis
|
||||
# Threshold covers the bottom side of the opening
|
||||
"ThresholdDepth": self.convert_si_to_unit(0.1),
|
||||
"ThresholdThickness": self.convert_si_to_unit(0.025), # by Z-axis
|
||||
# offset by Y-axis
|
||||
"ThresholdOffset": self.convert_si_to_unit(0.000),
|
||||
},
|
||||
"panel_properties": {
|
||||
"PanelDepth": self.convert_si_to_unit(0.035), # by Y
|
||||
"PanelWidth": 1.0, # as ratio to the clear door opening
|
||||
"FrameDepth": self.convert_si_to_unit(0.035), # by Y
|
||||
"FrameThickness": self.convert_si_to_unit(0.035), # by X
|
||||
# LEFT, MIDDLE, RIGHT, NOTDEFINED
|
||||
"PanelPosition": ..., # NEVER USED
|
||||
# defines the basic ways to describe how door panels operate
|
||||
# basically how it opens
|
||||
"PanelOperation": None, # NEVER USED
|
||||
"ShapeAspectStyle": None, # DEPRECATED
|
||||
},
|
||||
}
|
||||
)
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
builder = ShapeBuilder(self.file)
|
||||
overall_height = self.settings["overall_height"]
|
||||
overall_width = self.settings["overall_width"]
|
||||
door_type = self.settings["operation_type"]
|
||||
double_swing_door = "DOUBLE_SWING" in door_type
|
||||
double_door = "DOUBLE_DOOR" in door_type
|
||||
|
||||
if door_type not in SUPPORTED_DOOR_TYPES:
|
||||
raise NotImplementedError(f'Door type "{door_type}" is not currently supported.')
|
||||
|
||||
if self.settings["context"].TargetView == "ELEVATION_VIEW":
|
||||
rect = builder.rectangle(V(overall_width, 0, overall_height))
|
||||
representation_evelevation = builder.get_representation(self.settings["context"], rect)
|
||||
return representation_evelevation
|
||||
|
||||
panel_props = self.settings["panel_properties"]
|
||||
lining_props = self.settings["lining_properties"]
|
||||
|
||||
# lining params
|
||||
lining_depth = lining_props["LiningDepth"]
|
||||
lining_thickness_default = lining_props["LiningThickness"]
|
||||
lining_offset = lining_props["LiningOffset"]
|
||||
lining_to_panel_offset_x = lining_props["LiningToPanelOffsetX"]
|
||||
lining_to_panel_offset_y_full = lining_props["LiningToPanelOffsetY"]
|
||||
|
||||
transom_thickness = lining_props["TransomThickness"] / 2
|
||||
transfom_offset = lining_props["TransomOffset"]
|
||||
if transom_thickness == 0:
|
||||
transfom_offset = 0
|
||||
window_lining_height = overall_height - transfom_offset - transom_thickness
|
||||
|
||||
side_lining_thickness = lining_thickness_default
|
||||
panel_lining_overlap_x = max(lining_thickness_default - lining_to_panel_offset_x, 0)
|
||||
|
||||
top_lining_thickness = transom_thickness or lining_thickness_default
|
||||
panel_top_lining_overlap_x = max(top_lining_thickness - lining_to_panel_offset_x, 0)
|
||||
door_opening_width = overall_width - lining_to_panel_offset_x * 2
|
||||
if double_swing_door:
|
||||
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
|
||||
top_lining_thickness = top_lining_thickness - panel_top_lining_overlap_x
|
||||
|
||||
threshold_thickness = lining_props["ThresholdThickness"]
|
||||
threshold_depth = lining_props["ThresholdDepth"]
|
||||
threshold_offset = lining_props["ThresholdOffset"]
|
||||
threshold_width = overall_width - side_lining_thickness * 2
|
||||
|
||||
casing_thickness = lining_props["CasingThickness"]
|
||||
casing_depth = lining_props["CasingDepth"]
|
||||
|
||||
# panel params
|
||||
panel_depth = panel_props["PanelDepth"]
|
||||
panel_width = door_opening_width * panel_props["PanelWidth"]
|
||||
frame_depth = panel_props["FrameDepth"]
|
||||
frame_thickness = panel_props["FrameThickness"]
|
||||
frame_height = window_lining_height - lining_to_panel_offset_x * 2
|
||||
glass_thickness = self.convert_si_to_unit(0.01)
|
||||
|
||||
# handle dimensions (hardcoded)
|
||||
handle_size = self.convert_si_to_unit(V(120, 40, 20) * 0.001)
|
||||
handle_offset = self.convert_si_to_unit(V(60, 0, 1000) * 0.001) # to the handle center
|
||||
handle_center_offset = V(handle_size.y / 2, 0, handle_size.z) / 2
|
||||
|
||||
if transfom_offset:
|
||||
panel_height = transfom_offset + transom_thickness - lining_to_panel_offset_x - threshold_thickness
|
||||
lining_height = transfom_offset + transom_thickness
|
||||
else:
|
||||
panel_height = overall_height - lining_to_panel_offset_x - threshold_thickness
|
||||
lining_height = overall_height
|
||||
|
||||
# add lining
|
||||
lining_size = V(overall_width, lining_depth, lining_height)
|
||||
lining_thickness = [side_lining_thickness, top_lining_thickness]
|
||||
|
||||
def l_shape_check(lining_thickness):
|
||||
return lining_to_panel_offset_y_full < lining_depth and any(
|
||||
lining_to_panel_offset_x < th for th in lining_thickness
|
||||
)
|
||||
|
||||
# create 2d representation
|
||||
if self.settings["context"].TargetView == "PLAN_VIEW":
|
||||
items_2d = []
|
||||
door_items = []
|
||||
|
||||
# create lining
|
||||
if l_shape_check([side_lining_thickness]):
|
||||
lining_points = [
|
||||
V(0, 0),
|
||||
V(0, lining_depth),
|
||||
V(lining_to_panel_offset_x, lining_depth),
|
||||
V(lining_to_panel_offset_x, lining_to_panel_offset_y_full),
|
||||
V(lining_thickness_default, lining_to_panel_offset_y_full),
|
||||
V(lining_thickness_default, 0),
|
||||
]
|
||||
lining = builder.polyline(lining_points, closed=True)
|
||||
else:
|
||||
lining = builder.rectangle(V(side_lining_thickness, lining_depth))
|
||||
|
||||
items_2d.append(lining)
|
||||
items_2d.append(
|
||||
builder.mirror(lining, mirror_axes=V(1, 0), mirror_point=V(overall_width / 2, 0), create_copy=True)
|
||||
)
|
||||
|
||||
# TODO: make second swing lines dashed
|
||||
def create_ifc_door_panel_2d(panel_size, panel_position, door_swing_type):
|
||||
door_items = []
|
||||
panel_size = panel_size.yx
|
||||
# create semi-semi-circle
|
||||
if double_swing_door:
|
||||
trim_points_mask = (3, 1)
|
||||
second_swing_line = builder.polyline(
|
||||
points=(V(0, 0), V(0, -panel_size.y), V(panel_size.x, -panel_size.y))
|
||||
)
|
||||
door_items.append(second_swing_line)
|
||||
else:
|
||||
trim_points_mask = (0, 1)
|
||||
semicircle = builder.create_ellipse_curve(
|
||||
panel_size.y - panel_size.x,
|
||||
panel_size.y,
|
||||
trim_points_mask=trim_points_mask,
|
||||
position=V(panel_size.x, 0),
|
||||
)
|
||||
door_items.append(semicircle)
|
||||
|
||||
# create door
|
||||
door = builder.rectangle(panel_size)
|
||||
door_items.append(door)
|
||||
|
||||
builder.translate(door_items, panel_position)
|
||||
|
||||
if door_swing_type == "RIGHT":
|
||||
mirror_point = panel_position + V(panel_size.y / 2, 0)
|
||||
builder.mirror(door_items, mirror_axes=V(1, 0), mirror_point=mirror_point)
|
||||
return door_items
|
||||
|
||||
door_items = []
|
||||
panel_size = V(panel_width, panel_depth)
|
||||
panel_position = V(lining_to_panel_offset_x, lining_depth)
|
||||
|
||||
if double_door:
|
||||
panel_size.x = panel_size.x / 2
|
||||
door_items.extend(create_ifc_door_panel_2d(panel_size, panel_position, "LEFT"))
|
||||
|
||||
mirror_point = panel_position + V(door_opening_width / 2, 0)
|
||||
door_items.extend(
|
||||
builder.mirror(door_items, mirror_axes=V(1, 0), mirror_point=mirror_point, create_copy=True)
|
||||
)
|
||||
else:
|
||||
door_swing_type = "LEFT" if door_type.endswith("LEFT") else "RIGHT"
|
||||
door_items.extend(create_ifc_door_panel_2d(panel_size, panel_position, door_swing_type))
|
||||
items_2d.extend(door_items)
|
||||
|
||||
builder.translate(items_2d, V(0, lining_offset))
|
||||
representation_2d = builder.get_representation(self.settings["context"], items_2d)
|
||||
return representation_2d
|
||||
|
||||
lining_items = []
|
||||
main_lining_size = lining_size
|
||||
|
||||
# need to check offsets to decide whether lining should be rectangle
|
||||
# or L shaped
|
||||
if l_shape_check(lining_thickness):
|
||||
main_lining_size = lining_size.copy()
|
||||
main_lining_size.y = lining_to_panel_offset_y_full
|
||||
|
||||
second_lining_size = lining_size.copy()
|
||||
second_lining_size.y = lining_size.y - lining_to_panel_offset_y_full
|
||||
second_lining_position = V(0, lining_to_panel_offset_y_full, 0)
|
||||
second_lining_thickness = [min(th, lining_to_panel_offset_x) for th in lining_thickness]
|
||||
|
||||
second_lining = create_ifc_door_lining(
|
||||
builder, second_lining_size, second_lining_thickness, second_lining_position
|
||||
)
|
||||
lining_items.append(second_lining)
|
||||
|
||||
main_lining = create_ifc_door_lining(builder, main_lining_size, lining_thickness)
|
||||
lining_items.append(main_lining)
|
||||
|
||||
# add threshold
|
||||
if not threshold_thickness:
|
||||
threshold_items = []
|
||||
else:
|
||||
threshold_size = V(threshold_width, threshold_depth, threshold_thickness)
|
||||
threshold_position = V(side_lining_thickness, threshold_offset, 0)
|
||||
threshold_items = [create_ifc_box(builder, threshold_size, threshold_position)]
|
||||
|
||||
# add casings
|
||||
casing_items = []
|
||||
if not lining_offset and casing_thickness:
|
||||
casing_wall_overlap = max(casing_thickness - lining_thickness_default, 0)
|
||||
inner_casing_thickness = [
|
||||
casing_thickness - panel_lining_overlap_x,
|
||||
casing_thickness - panel_top_lining_overlap_x,
|
||||
]
|
||||
outer_casing_thickness = inner_casing_thickness.copy() if double_swing_door else casing_thickness
|
||||
|
||||
casing_size = V(overall_width + casing_wall_overlap * 2, casing_depth, overall_height + casing_wall_overlap)
|
||||
casing_position = V(-casing_wall_overlap, -casing_depth, 0)
|
||||
outer_casing = create_ifc_door_lining(builder, casing_size, outer_casing_thickness, casing_position)
|
||||
casing_items.append(outer_casing)
|
||||
|
||||
inner_casing_position = V(-casing_wall_overlap, lining_depth, 0)
|
||||
inner_casing = create_ifc_door_lining(builder, casing_size, inner_casing_thickness, inner_casing_position)
|
||||
casing_items.append(inner_casing)
|
||||
|
||||
def create_ifc_door_panel(panel_size, panel_position, door_swing_type):
|
||||
door_items = []
|
||||
# add door panel
|
||||
door_items.append(create_ifc_box(builder, panel_size, panel_position))
|
||||
# add door handle
|
||||
handle_points = [
|
||||
V(0, 0),
|
||||
V(0, -handle_size.y),
|
||||
V(handle_size.x, -handle_size.y),
|
||||
V(handle_size.x, -handle_size.y / 2),
|
||||
V(handle_size.y / 2, -handle_size.y / 2),
|
||||
V(handle_size.y / 2, 0),
|
||||
]
|
||||
handle_polyline = builder.polyline(handle_points, closed=True)
|
||||
|
||||
handle_position = panel_position + handle_offset - handle_center_offset
|
||||
|
||||
door_handle = builder.extrude(handle_polyline, handle_size.z, position=handle_position)
|
||||
door_items.append(door_handle)
|
||||
|
||||
if door_swing_type == "LEFT":
|
||||
builder.mirror(
|
||||
door_handle, mirror_axes=V(1, 0), mirror_point=panel_position.xy + V(panel_size.x / 2, 0)
|
||||
)
|
||||
|
||||
door_handle_mirrored = builder.mirror(
|
||||
door_handle,
|
||||
mirror_axes=V(0, 1),
|
||||
mirror_point=handle_position.xy + V(0, panel_size.y / 2),
|
||||
create_copy=True,
|
||||
)
|
||||
door_items.append(door_handle_mirrored)
|
||||
return door_items
|
||||
|
||||
door_items = []
|
||||
panel_size = V(panel_width, panel_depth, panel_height)
|
||||
panel_position = V(lining_to_panel_offset_x, lining_to_panel_offset_y_full, threshold_thickness)
|
||||
|
||||
if double_door:
|
||||
# TODO: keep a little space between doors for readibility?
|
||||
double_door_offset = self.convert_si_to_unit(0.001)
|
||||
panel_size.x = panel_size.x / 2 - double_door_offset
|
||||
door_items.extend(create_ifc_door_panel(panel_size, panel_position, "LEFT"))
|
||||
|
||||
mirror_point = panel_position + V(door_opening_width / 2, 0, 0)
|
||||
door_items.extend(
|
||||
builder.mirror(door_items, mirror_axes=V(1, 0), mirror_point=mirror_point.xy, create_copy=True)
|
||||
)
|
||||
else:
|
||||
door_swing_type = "LEFT" if door_type.endswith("LEFT") else "RIGHT"
|
||||
door_items.extend(create_ifc_door_panel(panel_size, panel_position, door_swing_type))
|
||||
|
||||
# add on top window
|
||||
if not transom_thickness:
|
||||
window_lining_items = []
|
||||
frame_items = []
|
||||
glass_items = []
|
||||
else:
|
||||
window_lining_thickness = [
|
||||
side_lining_thickness,
|
||||
lining_thickness_default,
|
||||
side_lining_thickness,
|
||||
transom_thickness,
|
||||
]
|
||||
window_lining_thickness.append(transom_thickness)
|
||||
window_lining_size = V(overall_width, lining_depth, window_lining_height)
|
||||
window_position = V(0, 0, overall_height - window_lining_height)
|
||||
frame_size = V(door_opening_width, frame_depth, frame_height)
|
||||
window_lining_items, frame_items, glass_items = create_ifc_window(
|
||||
builder,
|
||||
window_lining_size,
|
||||
window_lining_thickness,
|
||||
lining_to_panel_offset_x,
|
||||
lining_to_panel_offset_y_full,
|
||||
frame_size,
|
||||
frame_thickness,
|
||||
glass_thickness,
|
||||
window_position,
|
||||
)
|
||||
|
||||
lining_offset_items = lining_items + door_items + window_lining_items + frame_items + glass_items
|
||||
builder.translate(lining_offset_items, V(0, lining_offset, 0))
|
||||
|
||||
output_items = lining_offset_items + threshold_items + casing_items
|
||||
|
||||
representation = builder.get_representation(self.settings["context"], output_items)
|
||||
return representation
|
||||
|
||||
def convert_si_to_unit(self, value):
|
||||
si_conversion = 1 / self.settings["unit_scale"]
|
||||
if isinstance(value, Vector):
|
||||
return V(*[i * si_conversion for i in value])
|
||||
return value * si_conversion
|
||||
@@ -114,7 +114,16 @@ class Usecase:
|
||||
def create_model_representation(self):
|
||||
if self.settings["context"].is_a() == "IfcGeometricRepresentationContext":
|
||||
return self.create_variable_representation()
|
||||
if self.settings["context"].ContextIdentifier == "Annotation":
|
||||
elif self.settings["ifc_representation_class"] == "IfcTextLiteral":
|
||||
return self.create_text_representation(is_2d=False)
|
||||
elif self.settings["ifc_representation_class"] == "IfcGeometricCurveSet/IfcTextLiteral":
|
||||
shape_representation = self.create_geometric_curve_set_representation(is_2d=True)
|
||||
shape_representation.RepresentationType = "Annotation3D"
|
||||
items = list(shape_representation.Items)
|
||||
items.append(self.create_text())
|
||||
shape_representation.Items = items
|
||||
return shape_representation
|
||||
elif self.settings["context"].ContextIdentifier == "Annotation":
|
||||
return self.create_annotation3d_representation()
|
||||
elif self.settings["context"].ContextIdentifier == "Axis":
|
||||
return self.create_curve3d_representation()
|
||||
@@ -140,7 +149,7 @@ class Usecase:
|
||||
|
||||
def create_plan_representation(self):
|
||||
if self.settings["ifc_representation_class"] == "IfcTextLiteral":
|
||||
return self.create_text_representation()
|
||||
return self.create_text_representation(is_2d=True)
|
||||
elif self.settings["ifc_representation_class"] == "IfcGeometricCurveSet/IfcTextLiteral":
|
||||
shape_representation = self.create_geometric_curve_set_representation(is_2d=True)
|
||||
shape_representation.RepresentationType = "Annotation2D"
|
||||
@@ -153,7 +162,7 @@ class Usecase:
|
||||
elif self.settings["context"].ContextIdentifier == "Axis":
|
||||
return self.create_curve2d_representation()
|
||||
elif self.settings["context"].ContextIdentifier == "Body":
|
||||
pass
|
||||
return self.create_annotation2d_representation()
|
||||
elif self.settings["context"].ContextIdentifier == "Box":
|
||||
pass
|
||||
elif self.settings["context"].ContextIdentifier == "Clearance":
|
||||
@@ -194,11 +203,11 @@ class Usecase:
|
||||
},
|
||||
)
|
||||
|
||||
def create_text_representation(self):
|
||||
def create_text_representation(self, is_2d=False):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Annotation2D",
|
||||
"Annotation2D" if is_2d else "Annotation3D",
|
||||
[self.create_text()],
|
||||
)
|
||||
|
||||
@@ -684,12 +693,14 @@ class Usecase:
|
||||
|
||||
def create_annotation3d_representation(self):
|
||||
items = []
|
||||
curves = self.create_curves(should_exclude_faces=True, is_2d=False)
|
||||
if curves:
|
||||
items.append(self.file.createIfcGeometricCurveSet(curves))
|
||||
surfaces = self.create_curve_bounded_planes()
|
||||
if surfaces:
|
||||
items.append(self.file.createIfcGeometricSet(surfaces))
|
||||
if isinstance(self.settings["geometry"], bpy.types.Mesh) and len(self.settings["geometry"].polygons):
|
||||
items = self.create_annotation_fill_areas(is_2d=False)
|
||||
else:
|
||||
items = [self.file.createIfcGeometricCurveSet(self.create_curves(is_2d=False))]
|
||||
# TODO Unsure when it is appropriate to use curve bounded planes
|
||||
# surfaces = self.create_curve_bounded_planes()
|
||||
# if surfaces:
|
||||
# items.append(self.file.createIfcGeometricSet(surfaces))
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
|
||||
@@ -48,7 +48,7 @@ class Usecase:
|
||||
if self.file.schema == "IFC2X3":
|
||||
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
|
||||
else:
|
||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(points))
|
||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points))
|
||||
if self.settings["x_angle"]:
|
||||
extrusion_direction = self.file.createIfcDirection(
|
||||
(0.0, sin(self.settings["x_angle"]), cos(self.settings["x_angle"]))
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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.util.unit
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, V
|
||||
from itertools import chain
|
||||
from mathutils import Vector
|
||||
import collections
|
||||
|
||||
|
||||
# SCHEMAS describe panels setup
|
||||
# where:
|
||||
# - schema rows represent window X axis
|
||||
# - schema columns represent window Y axis
|
||||
# - order of rows is from top of the window to bottom
|
||||
|
||||
DEFAULT_PANEL_SCHEMAS = {
|
||||
"SINGLE_PANEL": [[0]],
|
||||
"DOUBLE_PANEL_HORIZONTAL": [[0], [1]],
|
||||
"DOUBLE_PANEL_VERTICAL": [[0, 1]],
|
||||
"TRIPLE_PANEL_BOTTOM": [[0, 1], [2, 2]],
|
||||
"TRIPLE_PANEL_TOP": [[0, 0], [1, 2]],
|
||||
"TRIPLE_PANEL_LEFT": [[0, 1], [0, 2]],
|
||||
"TRIPLE_PANEL_RIGHT": [[0, 1], [2, 1]],
|
||||
"TRIPLE_PANEL_HORIZONTAL": [[0], [1], [2]],
|
||||
"TRIPLE_PANEL_VERTICAL": [[0, 1, 2]],
|
||||
}
|
||||
|
||||
|
||||
def create_ifc_window_frame_simple(builder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
|
||||
"""`thickness` of the profile is defined as list in the following order:
|
||||
`(LEFT, TOP, RIGHT, BOTTOM)`
|
||||
|
||||
`thickness` can be also defined just as 1 float value.
|
||||
"""
|
||||
|
||||
if not isinstance(thickness, collections.abc.Iterable):
|
||||
thickness = [thickness] * 4
|
||||
|
||||
th_left, th_up, th_right, th_bottom = thickness
|
||||
|
||||
panel_rect = builder.rectangle(size=size * V(1, 0, 1))
|
||||
|
||||
inner_rect_size = size - V(th_left + th_right, 0, th_bottom + th_up)
|
||||
inner_rect = builder.rectangle(size=inner_rect_size * V(1, 0, 1), position=V(th_left, 0, th_bottom))
|
||||
|
||||
panel_profile = builder.profile(panel_rect, inner_curves=inner_rect)
|
||||
panel_extruded = builder.extrude(panel_profile, size.y, extrusion_vector=V(0, 1, 0), position=position)
|
||||
return panel_extruded
|
||||
|
||||
|
||||
def window_l_shape_check(
|
||||
lining_to_panel_offset_y_full,
|
||||
lining_depth,
|
||||
lining_to_panel_offset_x,
|
||||
lining_thickness: list,
|
||||
):
|
||||
"""`lining_thickness` expected to be defined as a list,
|
||||
similarly to `create_ifc_window_frame_simple` `thickness` argument"""
|
||||
l_shape_check = lining_to_panel_offset_y_full < lining_depth and any(
|
||||
lining_to_panel_offset_x < th for th in lining_thickness
|
||||
)
|
||||
return l_shape_check
|
||||
|
||||
|
||||
def create_ifc_window(
|
||||
builder,
|
||||
lining_size: Vector,
|
||||
lining_thickness: list,
|
||||
lining_to_panel_offset_x,
|
||||
lining_to_panel_offset_y_full,
|
||||
frame_size: Vector,
|
||||
frame_thickness,
|
||||
glass_thickness,
|
||||
position: Vector,
|
||||
):
|
||||
"""`lining_thickness` expected to be defined as a list,
|
||||
similarly to `create_ifc_window_frame_simple` `thickness` argument"""
|
||||
lining_items = []
|
||||
main_lining_size = lining_size
|
||||
|
||||
# need to check offsets to decide whether lining should be rectangle
|
||||
# or L shaped
|
||||
l_shape_check = window_l_shape_check(
|
||||
lining_to_panel_offset_y_full,
|
||||
lining_size.y,
|
||||
lining_to_panel_offset_x,
|
||||
lining_thickness,
|
||||
)
|
||||
|
||||
if l_shape_check:
|
||||
main_lining_size = lining_size.copy()
|
||||
main_lining_size.y = lining_to_panel_offset_y_full
|
||||
|
||||
second_lining_size = lining_size.copy()
|
||||
second_lining_size.y = lining_size.y - lining_to_panel_offset_y_full
|
||||
second_lining_position = V(0, lining_to_panel_offset_y_full, 0)
|
||||
second_lining_thickness = [min(th, lining_to_panel_offset_x) for th in lining_thickness]
|
||||
|
||||
second_lining = create_ifc_window_frame_simple(
|
||||
builder, second_lining_size, second_lining_thickness, second_lining_position
|
||||
)
|
||||
lining_items.append(second_lining)
|
||||
|
||||
main_lining = create_ifc_window_frame_simple(builder, main_lining_size, lining_thickness)
|
||||
lining_items.append(main_lining)
|
||||
|
||||
frame_position = V(
|
||||
lining_to_panel_offset_x,
|
||||
lining_to_panel_offset_y_full,
|
||||
lining_to_panel_offset_x,
|
||||
)
|
||||
|
||||
frame_extruded = create_ifc_window_frame_simple(builder, frame_size, frame_thickness, frame_position)
|
||||
|
||||
glass_position = frame_position + V(0, frame_size.y / 2 - glass_thickness / 2, 0)
|
||||
glass_rect = builder.deep_copy(frame_extruded.SweptArea.InnerCurves[0])
|
||||
glass = builder.extrude(
|
||||
glass_rect,
|
||||
glass_thickness,
|
||||
extrusion_vector=V(0, 1, 0),
|
||||
position=glass_position,
|
||||
)
|
||||
|
||||
output_items = [lining_items, [frame_extruded], [glass]]
|
||||
builder.translate(chain(*output_items), position)
|
||||
|
||||
return output_items
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
"""units in settings expected to be in ifc project units"""
|
||||
self.file = file
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindow.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowTypePartitioningEnum.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowLiningProperties.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowPanelProperties.htm
|
||||
self.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(self.file)}
|
||||
self.settings.update(
|
||||
{
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
# SINGLE_PANEL, DOUBLE_PANEL_HORIZONTAL, DOUBLE_PANEL_VERTICAL,
|
||||
# TRIPLE_PANEL_BOTTOM, TRIPLE_PANEL_HORIZONTAL, TRIPLE_PANEL_LEFT,
|
||||
# TRIPLE_PANEL_RIGHT, TRIPLE_PANEL_TOP, TRIPLE_PANEL_VERTICAL
|
||||
"partition_type": "SINGLE_PANEL",
|
||||
"overall_height": self.convert_si_to_unit(0.9),
|
||||
"overall_width": self.convert_si_to_unit(0.6),
|
||||
"lining_properties": {
|
||||
"LiningDepth": self.convert_si_to_unit(0.050),
|
||||
"LiningThickness": self.convert_si_to_unit(0.050),
|
||||
"LiningOffset": self.convert_si_to_unit(0.050), # offset to the wall
|
||||
# offset from the wall
|
||||
"LiningToPanelOffsetX": self.convert_si_to_unit(0.025),
|
||||
# offset from the lining
|
||||
# that way it allows you to define overall_depth constant between all panels
|
||||
# and still have panels with different size:
|
||||
# overall_depth = lining_depth + offset_y
|
||||
# full offset from X axis = overall_depth - frame_depth
|
||||
"LiningToPanelOffsetY": self.convert_si_to_unit(0.025),
|
||||
# applies to DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
|
||||
# TriplePanelLeft, TriplePanelRight
|
||||
# mullion - horizontal distance between panels
|
||||
"MullionThickness": self.convert_si_to_unit(0.050),
|
||||
# distance from the first lining to the mullion center
|
||||
"FirstMullionOffset": self.convert_si_to_unit(0.3),
|
||||
# applies to TriplePanelVertical
|
||||
# distance from the first lining to the second mullion center
|
||||
"SecondMullionOffset": self.convert_si_to_unit(0.45),
|
||||
# applies to DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
|
||||
# TriplePanelLeft, TriplePanelRight
|
||||
# works similar way to mullion
|
||||
"TransomThickness": self.convert_si_to_unit(0.050),
|
||||
"FirstTransomOffset": self.convert_si_to_unit(0.3),
|
||||
# applies to TriplePanelHorizontal
|
||||
"SecondTransomOffset": self.convert_si_to_unit(0.6),
|
||||
"ShapeAspectStyle": None, # DEPRECATED
|
||||
},
|
||||
"panel_properties": [
|
||||
{
|
||||
"FrameDepth": self.convert_si_to_unit(0.035), # by Y
|
||||
"FrameThickness": self.convert_si_to_unit(0.035), # by X
|
||||
# BOTTOM, LEFT, MIDDLE, RIGHT, TOP
|
||||
"PanelPosition": ..., # NEVER USED
|
||||
# defines the basic ways to describe how window panels operate
|
||||
# how it's hanged, how it opens
|
||||
"OperationType": None, # NEVER USED
|
||||
"ShapeAspectStyle": None, # DEPRECATED
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[self.settings["partition_type"]]
|
||||
|
||||
def execute(self):
|
||||
builder = ShapeBuilder(self.file)
|
||||
overall_height = self.settings["overall_height"]
|
||||
overall_width = self.settings["overall_width"]
|
||||
|
||||
if self.settings["context"].TargetView == "ELEVATION_VIEW":
|
||||
rect = builder.rectangle(V(overall_width, 0, overall_height))
|
||||
representation_evelevation = builder.get_representation(self.settings["context"], rect)
|
||||
return representation_evelevation
|
||||
|
||||
panel_schema = self.settings["panel_schema"]
|
||||
panels = self.settings["panel_properties"]
|
||||
accumulated_height = [0] * len(panel_schema[0])
|
||||
built_panels = []
|
||||
window_items = []
|
||||
|
||||
lining_props = self.settings["lining_properties"]
|
||||
lining_thickness = lining_props["LiningThickness"]
|
||||
lining_depth = lining_props["LiningDepth"]
|
||||
lining_offset = lining_props["LiningOffset"]
|
||||
lining_to_panel_offset_x = lining_props["LiningToPanelOffsetX"]
|
||||
lining_to_panel_offset_y = lining_props["LiningToPanelOffsetY"]
|
||||
overall_depth = lining_depth + lining_to_panel_offset_y
|
||||
|
||||
mullion_thickness = lining_props["MullionThickness"] / 2
|
||||
first_mullion_offset = lining_props["FirstMullionOffset"]
|
||||
second_mullion_offset = lining_props["SecondMullionOffset"]
|
||||
transom_thickness = lining_props["TransomThickness"] / 2
|
||||
first_transom_offset = lining_props["FirstTransomOffset"]
|
||||
second_transom_offset = lining_props["SecondTransomOffset"]
|
||||
glass_thickness = self.convert_si_to_unit(0.01)
|
||||
|
||||
panel_schema = list(reversed(panel_schema))
|
||||
|
||||
# create 2d representation
|
||||
def create_ifc_window_2d_representation():
|
||||
items_2d = []
|
||||
|
||||
top_row = panel_schema[-1]
|
||||
unique_cols = len(set(top_row))
|
||||
built_panels = []
|
||||
accumulated_width = 0
|
||||
|
||||
for column_i, panel_i in enumerate(top_row):
|
||||
cur_panel_items = []
|
||||
|
||||
# lists represent left and right linings
|
||||
window_lining_thickness = [lining_thickness] * 2
|
||||
closed_lining = [True] * 2
|
||||
|
||||
if panel_i in built_panels:
|
||||
continue
|
||||
|
||||
if unique_cols > 1:
|
||||
if column_i == 0:
|
||||
panel_width = first_mullion_offset
|
||||
elif column_i == unique_cols - 1:
|
||||
panel_width = overall_width - accumulated_width
|
||||
else:
|
||||
panel_width = second_mullion_offset - accumulated_width
|
||||
|
||||
# mullion thickness
|
||||
if column_i != 0:
|
||||
window_lining_thickness[0] = mullion_thickness # left column
|
||||
closed_lining[0] = False
|
||||
if column_i != unique_cols - 1:
|
||||
window_lining_thickness[1] = mullion_thickness # right column
|
||||
closed_lining[1] = False
|
||||
else:
|
||||
panel_width = overall_width
|
||||
|
||||
frame_depth = panels[panel_i]["FrameDepth"]
|
||||
frame_thickness = panels[panel_i]["FrameThickness"]
|
||||
lining_to_panel_offset_y_full = overall_depth - frame_depth
|
||||
|
||||
# add lining
|
||||
cur_panel_items.append(
|
||||
builder.polyline(
|
||||
[
|
||||
V(window_lining_thickness[0], 0),
|
||||
V(panel_width - window_lining_thickness[1], 0),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def get_lining_shape(lining_thickness, closed=True, mirror=False):
|
||||
l_shape_check = window_l_shape_check(
|
||||
lining_to_panel_offset_y_full,
|
||||
lining_depth,
|
||||
lining_to_panel_offset_x,
|
||||
[lining_thickness],
|
||||
)
|
||||
if l_shape_check:
|
||||
lining_shape = builder.polyline(
|
||||
[
|
||||
V(0, lining_depth),
|
||||
V(lining_to_panel_offset_x, lining_depth),
|
||||
V(
|
||||
lining_to_panel_offset_x,
|
||||
lining_to_panel_offset_y_full,
|
||||
),
|
||||
V(lining_thickness, lining_to_panel_offset_y_full),
|
||||
V(lining_thickness, 0),
|
||||
V(0, 0),
|
||||
],
|
||||
closed=closed,
|
||||
)
|
||||
else:
|
||||
lining_shape = builder.polyline(
|
||||
[
|
||||
V(0, lining_depth),
|
||||
V(lining_thickness, lining_depth),
|
||||
V(lining_thickness, 0),
|
||||
V(0, 0),
|
||||
],
|
||||
closed=closed,
|
||||
)
|
||||
|
||||
if mirror:
|
||||
builder.mirror(
|
||||
lining_shape,
|
||||
mirror_axes=V(1, 0),
|
||||
mirror_point=V(panel_width / 2, 0),
|
||||
)
|
||||
|
||||
return lining_shape
|
||||
|
||||
cur_panel_items.extend(
|
||||
[
|
||||
get_lining_shape(window_lining_thickness[0], closed=closed_lining[0]),
|
||||
get_lining_shape(
|
||||
window_lining_thickness[1],
|
||||
closed=closed_lining[1],
|
||||
mirror=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# add frame
|
||||
frame_items = []
|
||||
frame_position = V(lining_to_panel_offset_x, lining_to_panel_offset_y_full)
|
||||
frame_width = panel_width - lining_to_panel_offset_x * 2
|
||||
|
||||
frame_vertical = builder.rectangle(size=V(frame_thickness, frame_depth))
|
||||
frame_items.extend(
|
||||
[
|
||||
frame_vertical,
|
||||
builder.mirror(
|
||||
frame_vertical,
|
||||
mirror_axes=V(1, 0),
|
||||
mirror_point=V(frame_width / 2, 0),
|
||||
create_copy=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
frame_horizontal = builder.polyline([V(frame_thickness, 0), V(frame_width - frame_thickness, 0)])
|
||||
frame_items.extend(
|
||||
[
|
||||
frame_horizontal,
|
||||
builder.translate(frame_horizontal, V(0, frame_depth), create_copy=True),
|
||||
]
|
||||
)
|
||||
# glass
|
||||
frame_items.append(builder.translate(frame_horizontal, V(0, frame_depth / 2), create_copy=True))
|
||||
|
||||
builder.translate(frame_items, frame_position)
|
||||
cur_panel_items.extend(frame_items)
|
||||
|
||||
builder.translate(cur_panel_items, V(accumulated_width, 0))
|
||||
|
||||
accumulated_width += panel_width
|
||||
built_panels.append(panel_i)
|
||||
items_2d.extend(cur_panel_items)
|
||||
|
||||
builder.translate(items_2d, V(0, lining_offset))
|
||||
representation_2d = builder.get_representation(self.settings["context"], items_2d)
|
||||
return representation_2d
|
||||
|
||||
if self.settings["context"].TargetView == "PLAN_VIEW":
|
||||
return create_ifc_window_2d_representation()
|
||||
|
||||
# TODO: need more readable way to define panel width and height
|
||||
unique_rows_in_col = [
|
||||
len(set(row[column_i] for row in panel_schema)) for column_i in range(len(panel_schema[0]))
|
||||
]
|
||||
|
||||
for row_i, panel_row in enumerate(panel_schema):
|
||||
accumulated_width = 0
|
||||
unique_cols = len(set(panel_row))
|
||||
|
||||
for column_i, panel_i in enumerate(panel_row):
|
||||
# calculate current panel dimensions
|
||||
window_lining_thickness = [lining_thickness] * 4
|
||||
|
||||
if unique_cols > 1:
|
||||
# panel_width
|
||||
if column_i == 0:
|
||||
panel_width = first_mullion_offset
|
||||
elif column_i == unique_cols - 1:
|
||||
panel_width = overall_width - accumulated_width
|
||||
else:
|
||||
panel_width = second_mullion_offset - accumulated_width
|
||||
|
||||
# mullion thickness
|
||||
if column_i != 0:
|
||||
window_lining_thickness[0] = mullion_thickness # left column
|
||||
if column_i != unique_cols - 1:
|
||||
window_lining_thickness[2] = mullion_thickness # right column
|
||||
else:
|
||||
panel_width = overall_width
|
||||
|
||||
if unique_rows_in_col[column_i] > 1:
|
||||
if row_i == 0:
|
||||
panel_height = first_transom_offset
|
||||
elif row_i == unique_rows_in_col[column_i] - 1:
|
||||
panel_height = overall_height - accumulated_height[column_i]
|
||||
else:
|
||||
panel_height = second_transom_offset - accumulated_height[column_i]
|
||||
|
||||
# transom thickness
|
||||
if row_i != 0:
|
||||
window_lining_thickness[3] = transom_thickness # bottom row
|
||||
if row_i != unique_rows_in_col[column_i] - 1:
|
||||
window_lining_thickness[1] = transom_thickness # top row
|
||||
else:
|
||||
panel_height = overall_height
|
||||
|
||||
if panel_i in built_panels:
|
||||
accumulated_height[column_i] += panel_height
|
||||
accumulated_width += panel_width
|
||||
continue
|
||||
|
||||
cur_panel = panels[panel_i]
|
||||
frame_depth = cur_panel["FrameDepth"]
|
||||
frame_thickness = cur_panel["FrameThickness"]
|
||||
lining_to_panel_offset_y_full = overall_depth - frame_depth
|
||||
current_items = []
|
||||
|
||||
frame_width = panel_width - lining_to_panel_offset_x * 2
|
||||
frame_height = panel_height - lining_to_panel_offset_x * 2
|
||||
|
||||
window_lining_size = V(panel_width, lining_depth, panel_height)
|
||||
frame_size = V(frame_width, frame_depth, frame_height)
|
||||
window_panel_position = V(accumulated_width, 0, accumulated_height[column_i])
|
||||
|
||||
# create window panel
|
||||
current_window_items = create_ifc_window(
|
||||
builder,
|
||||
window_lining_size,
|
||||
window_lining_thickness,
|
||||
lining_to_panel_offset_x,
|
||||
lining_to_panel_offset_y_full,
|
||||
frame_size,
|
||||
frame_thickness,
|
||||
glass_thickness,
|
||||
window_panel_position,
|
||||
)
|
||||
built_panels.append(panel_i)
|
||||
window_items.extend(chain(*current_window_items))
|
||||
|
||||
accumulated_height[column_i] += panel_height
|
||||
accumulated_width += panel_width
|
||||
|
||||
builder.translate(window_items, V(0, lining_offset, 0)) # wall offset
|
||||
representation = builder.get_representation(self.settings["context"], window_items)
|
||||
return representation
|
||||
|
||||
def convert_si_to_unit(self, value):
|
||||
return value / self.settings["unit_scale"]
|
||||
@@ -1,52 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
products = {}
|
||||
representations = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.products = {}
|
||||
cls.representations = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
if not file:
|
||||
return
|
||||
cls.products[product_id] = []
|
||||
product = file.by_id(product_id)
|
||||
representations = []
|
||||
if product.is_a("IfcProduct") and product.Representation:
|
||||
representations = product.Representation.Representations
|
||||
elif product.is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
|
||||
for representation in representations:
|
||||
c = representation.ContextOfItems
|
||||
rep_id = int(representation.id())
|
||||
cls.representations[rep_id] = {
|
||||
"RepresentationIdentifier": representation.RepresentationIdentifier,
|
||||
"RepresentationType": representation.RepresentationType,
|
||||
"ContextOfItems": {
|
||||
"ContextType": c.ContextType,
|
||||
"ContextIdentifier": c.ContextIdentifier,
|
||||
"TargetView": c.TargetView if c.is_a("IfcGeometricRepresentationSubContext") else "",
|
||||
},
|
||||
}
|
||||
cls.products[product_id].append(rep_id)
|
||||
@@ -76,9 +76,7 @@ class Usecase:
|
||||
matrix[2][3] *= self.unit_scale
|
||||
|
||||
def get_placement_rel_to(self):
|
||||
if getattr(self.settings["product"], "ContainedInStructure", None):
|
||||
return self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement
|
||||
elif getattr(self.settings["product"], "Decomposes", None):
|
||||
if getattr(self.settings["product"], "Decomposes", None):
|
||||
relating_object = self.settings["product"].Decomposes[0].RelatingObject
|
||||
return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif getattr(self.settings["product"], "Nests", None):
|
||||
@@ -96,6 +94,9 @@ class Usecase:
|
||||
elif getattr(self.settings["product"], "ProjectsElements", None):
|
||||
relating_object = self.settings["product"].ProjectsElements[0].RelatingElement
|
||||
return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif getattr(self.settings["product"], "ContainedInStructure", None):
|
||||
return self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement
|
||||
|
||||
|
||||
def get_children_settings(self, placement):
|
||||
if not placement:
|
||||
|
||||
@@ -28,9 +28,6 @@ class Usecase:
|
||||
def execute(self):
|
||||
mapping_source = self.get_mapping_source()
|
||||
|
||||
if not mapping_source:
|
||||
return
|
||||
|
||||
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
|
||||
y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
|
||||
@@ -52,3 +49,10 @@ class Usecase:
|
||||
for inverse in self.file.get_inverse(self.settings["representation"]):
|
||||
if inverse.is_a("IfcRepresentationMap"):
|
||||
return inverse
|
||||
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
|
||||
z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
|
||||
mapping_origin = self.file.createIfcAxis2Placement3D(zero, z_axis, x_axis)
|
||||
return self.file.createIfcRepresentationMap(
|
||||
MappingOrigin=mapping_origin, MappedRepresentation=self.settings["representation"]
|
||||
)
|
||||
|
||||
@@ -18,7 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file):
|
||||
"""Add empty georeferencing entities to a model
|
||||
|
||||
By default, models are not georeferenced. Georeferencing requires two
|
||||
entities: a definition of the projected coordinated reference system
|
||||
(CRS) used, and the transformation parameters between any local coordinate
|
||||
system and that projected CRS if any.
|
||||
|
||||
This function will create the entities to store the projected CRS and
|
||||
map conversion transformation, but will leave all the parameters blank.
|
||||
It is this the users responsibility to specify the correct
|
||||
georeferencing parameters. See
|
||||
ifcopenshell.api.georeference.edit_georeferencing.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
"""
|
||||
self.file = file
|
||||
|
||||
def execute(self):
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
map_conversion = {}
|
||||
projected_crs = {}
|
||||
true_north = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.map_conversion = {}
|
||||
cls.projected_crs = {}
|
||||
cls.true_north = None
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
if not file:
|
||||
return
|
||||
cls.map_conversion = {}
|
||||
cls.projected_crs = {}
|
||||
cls.true_north = {}
|
||||
if file.schema == "IFC2X3":
|
||||
return
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if not context.HasCoordinateOperation:
|
||||
continue
|
||||
map_conversion = context.HasCoordinateOperation[0]
|
||||
cls.map_conversion = map_conversion.get_info()
|
||||
cls.map_conversion["SourceCRS"] = cls.map_conversion["SourceCRS"].id()
|
||||
cls.map_conversion["TargetCRS"] = cls.map_conversion["TargetCRS"].id()
|
||||
cls.projected_crs = map_conversion.TargetCRS.get_info()
|
||||
if cls.projected_crs["MapUnit"]:
|
||||
cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info()
|
||||
break
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if not context.TrueNorth:
|
||||
continue
|
||||
cls.true_north = context.TrueNorth.DirectionRatios
|
||||
break
|
||||
cls.is_loaded = True
|
||||
@@ -18,15 +18,75 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, map_conversion=None, projected_crs=None, true_north=None):
|
||||
"""Edits the attributes of a map conversion, projected CRS, and true north
|
||||
|
||||
Setting the correct georeferencing parameters is a complex topic and
|
||||
should ideally be done with three parties present: the lead architect,
|
||||
surveyor, and a third-party digital engineer with expertise in IFC to
|
||||
moderate. For more information, read the BlenderBIM Add-on documentation
|
||||
for Georeferencing:
|
||||
https://blenderbim.org/docs/users/georeferencing.html
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcMapConversion, consult the IFC documentation.
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcProjectedCRS, consult the IFC documentation.
|
||||
|
||||
True north is defined as a unitised 2D vector pointing to true north.
|
||||
Note that true north is not part of georeferencing, and is only
|
||||
optionally provided as a reference value, typically for solar analysis.
|
||||
|
||||
See ifcopenshell.util.geolocation for more utilities to convert to and
|
||||
from local and map coordinates to check your results.
|
||||
|
||||
:param map_conversion: The IfcMapConversion dictionary of attribute
|
||||
names and values you want to edit.
|
||||
:type map_conversion: dict, optional
|
||||
:param projected_crs: The IfcProjectedCRS dictionary of attribute
|
||||
names and values you want to edit.
|
||||
:type projected_crs: dict, optional
|
||||
:param true_north: A unitised 2D vector, where each ordinate is a float
|
||||
:type true_north: list[float]
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
# This is the simplest scenario, a defined CRS (GDA2020 / MGA Zone
|
||||
# 56, typically used in Sydney, Australia) but with no local
|
||||
# coordinates. This is only recommended for horizontal construction
|
||||
# projects, not for vertical construction (such as buildings).
|
||||
ifcopenshell.api.run("georeference.edit_georeferencing", model,
|
||||
projected_crs={"Name": "EPSG:7856"})
|
||||
|
||||
# For buildings, it is almost always recommended to specify map
|
||||
# conversion parameters to a false origin and orientation to project
|
||||
# north. See the diagram in the BlenderBIM Add-on Georeferencing
|
||||
# documentation for correct calculation of the X Axis Abcissa and
|
||||
# Ordinate.
|
||||
ifcopenshell.api.run("georeference.edit_georeferencing", model,
|
||||
projected_crs={"Name": "EPSG:7856"},
|
||||
map_conversion={
|
||||
"Eastings": 335087.17, # The architect nominates a false origin
|
||||
"Northings": 6251635.41, # The architect nominates a false origin
|
||||
# Note: this is the angle difference between Project North
|
||||
# and Grid North. Remember: True North should never be used!
|
||||
"XAxisAbscissa": cos(radians(-30)), # The architect nominates a project north
|
||||
"XAxisOrdinate": sin(radians(-30)), # The architect nominates a project north
|
||||
"Scale": 0.99956, # Ask your surveyor for your site's average combined scale factor!
|
||||
})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"map_conversion": {},
|
||||
"projected_crs": {},
|
||||
"true_north": [],
|
||||
"map_conversion": map_conversion or {},
|
||||
"projected_crs": projected_crs or {},
|
||||
"true_north": true_north or [],
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
map_conversion = self.file.by_type("IfcMapConversion")[0]
|
||||
|
||||
@@ -18,7 +18,21 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file):
|
||||
"""Remove georeferencing data
|
||||
|
||||
All georeferencing parameters such as projected CRS and map conversion
|
||||
data will be lost.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
# Let's change our mind
|
||||
ifcopenshell.api.run("georeference.remove_georeferencing", model)
|
||||
"""
|
||||
self.file = file
|
||||
|
||||
def execute(self):
|
||||
|
||||
@@ -23,14 +23,44 @@ from mathutils import Matrix # For now, we depend on Blender
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, axis_curve=None, grid_axis=None):
|
||||
"""Adds curve geometry to a grid axis to represent the axis extents
|
||||
|
||||
This currently depends on the Blender geometry kernel to function.
|
||||
|
||||
An IFC grid will have a minimum of two axes (typically perpendicular). Each
|
||||
axis will then have a line which represents the extents of the axis.
|
||||
|
||||
:param axis_curve: The Blender object that contains a mesh data block with a
|
||||
single edge.
|
||||
:type axis_curve: bpy.types.Object
|
||||
:param grid_axis: The IfcGridAxis element to add geometry to.
|
||||
:type grid_axis: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# A pretty standard rectangular grid, with only two axes.
|
||||
grid = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcGrid")
|
||||
axis_a = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="A", uvw_axes="UAxes", grid=grid)
|
||||
axis_1 = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="1", uvw_axes="VAxes", grid=grid)
|
||||
|
||||
# Assume you have these Blender objects in your active Blender session
|
||||
obj1 = bpy.data.objects.get("AxisA")
|
||||
obj2 = bpy.data.objects.get("Axis1")
|
||||
ifcopenshell.api.run("grid.create_axis_curve", model, axis_curve=obj1, grid_axis=axis_a)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", model, axis_curve=obj2, grid_axis=axis_1)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"axis_curve": None, # A Blender object
|
||||
"grid_axis": None,
|
||||
"axis_curve": axis_curve, # A Blender object
|
||||
"grid_axis": grid_axis,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
existing_curve = self.settings["grid_axis"].AxisCurve
|
||||
|
||||
@@ -18,16 +18,62 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, axis_tag=None, same_sense=None, uvw_axes=None, grid=None):
|
||||
"""Adds a new grid axis to a grid
|
||||
|
||||
An IFC grid will typically have a minimum of two axes which will be
|
||||
perpendicular to one another. Grids may be rectangular (typically
|
||||
perpendicular lines), radial (where one set of axes is a circle and the
|
||||
other is a line), or triangular (three sets of axes, each at a different
|
||||
angle to one another).
|
||||
|
||||
For a simple rectangular grid, the "UAxes" are a set of one or more
|
||||
horizontal axes, which are typically labeled with the convention of A,
|
||||
B, C, etc. The "VAxes" is another set of one or more vertical axes,
|
||||
typically labeled with the convention of 1, 2, 3, etc. These axes are
|
||||
horizontal or vertical relative to project north.
|
||||
|
||||
For a radial grid, the "UAxes" are straight lines, typically radiating
|
||||
from a central point. The "VAxes" are circular perimeters, with the
|
||||
center of these circles being the same central point.
|
||||
|
||||
For a triangular grid, the UAxes, VAxes, and WAxes are all sets of one
|
||||
or more straight lines.
|
||||
|
||||
:param axis_tag: The name of the axis, that would typically be labeled
|
||||
on drawings or described on site during coordination, such as A, B,
|
||||
C, 1, 2, 3, etc. Defaults to "A".
|
||||
:type axis_tag: str, optional
|
||||
:param same_sense: Determines whether the direction of the axis's line
|
||||
is reversed. True means the direction the geometry is defined in
|
||||
represents the direction of the axis. False means the direction is
|
||||
reversed. Leave as True if unsure. Defaults to "True".
|
||||
:type same_sense: bool, optional
|
||||
:param uvw_axes: Choose from "UAxes", "VAxes" or "WAxes" depending on
|
||||
which set of axes the new axis you are adding should belong to.
|
||||
Defaults to "UAxes".
|
||||
:type uvw_axes: str, optional
|
||||
:param grid: The IfcGrid you are adding the axis to.
|
||||
:type grid: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcGridAxis
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
# A pretty standard rectangular grid, with only two axes.
|
||||
grid = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcGrid")
|
||||
axis_a = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="A", uvw_axes="UAxes", grid=grid)
|
||||
axis_1 = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="1", uvw_axes="VAxes", grid=grid)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"axis_tag": "A",
|
||||
"same_sense": True,
|
||||
"uvw_axes": "UAxes", # Choose which axes
|
||||
"grid": None,
|
||||
"axis_tag": axis_tag or "A",
|
||||
"same_sense": same_sense or True,
|
||||
"uvw_axes": uvw_axes or "UAxes", # Choose which axes
|
||||
"grid": grid,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
element = self.file.create_entity(
|
||||
|
||||
@@ -20,11 +20,32 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, axis=None):
|
||||
"""Removes a grid axis from a grid
|
||||
|
||||
:param axis: The IfcGridAxis you want to remove.
|
||||
:type axis: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
# A pretty standard rectangular grid, with only two axes.
|
||||
grid = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcGrid")
|
||||
axis_a = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="A", uvw_axes="UAxes", grid=grid)
|
||||
axis_1 = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="1", uvw_axes="VAxes", grid=grid)
|
||||
|
||||
# Let's create a third so we can remvoe it later
|
||||
axis_2 = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="2", uvw_axes="VAxes", grid=grid)
|
||||
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", model, axis=axis_2)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"axis": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"axis": axis}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["axis"].AxisCurve)) == 1:
|
||||
|
||||
@@ -21,14 +21,35 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, Name="Unnamed", Description=None):
|
||||
"""Adds a new group
|
||||
|
||||
An IFC group is an arbitrary collection of products, which are typically
|
||||
physical. It may be used when there is no other more specific group
|
||||
which may be used. Other types of groups include distribution systems,
|
||||
which group together products that are connected and circulate a medium
|
||||
(such as fluid or electricity), or zones, which group together spaces,
|
||||
or structural load groups, which group together loads for structural
|
||||
analysis, or inventories, which are groups of assets.
|
||||
|
||||
:param Name: The name of the group. Defaults to "Unnamed"
|
||||
:type Name: str, optional
|
||||
:param Description: The description of the purpose of the group.
|
||||
:type Description: str, optional
|
||||
:return: The newly created IfcGroup
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"Name": "Unnamed",
|
||||
"Description": "",
|
||||
"Name": Name or "Unnamed",
|
||||
"Description": Description,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(
|
||||
|
||||
@@ -21,14 +21,32 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, products=None, group=None):
|
||||
"""Assigns products to a group
|
||||
|
||||
If a product is already assigned to the group, it will not be assigned
|
||||
twice.
|
||||
|
||||
:param products: A list of IfcProduct elements to assign to the group
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param group: The IfcGroup to assign the products to
|
||||
:type group: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAssignsToGroup relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
|
||||
ifcopenshell.api.run("group.assign_group", model,
|
||||
products=model.by_type("IfcFurniture"), group=group)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": None,
|
||||
"group": None,
|
||||
"products": products,
|
||||
"group": group,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["group"].IsGroupedBy:
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Data:
|
||||
is_loaded = False
|
||||
products = {}
|
||||
groups = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.products = {}
|
||||
cls.groups = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
cls.products = {}
|
||||
cls.groups = {}
|
||||
for group in file.by_type("IfcGroup", include_subtypes=False):
|
||||
if group.IsGroupedBy:
|
||||
for rel in group.IsGroupedBy:
|
||||
for product in rel.RelatedObjects:
|
||||
cls.products.setdefault(product.id(), []).append(group.id())
|
||||
data = group.get_info()
|
||||
data["HasAssignments"] = group.HasAssignments
|
||||
data["IsGroupedBy"] = group.IsGroupedBy
|
||||
del data["OwnerHistory"]
|
||||
cls.groups[group.id()] = data
|
||||
cls.is_loaded = True
|
||||
@@ -18,12 +18,29 @@
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, group=None, attributes=None):
|
||||
"""Edits the attributes of an IfcGroup
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcGroup, consult the IFC documentation.
|
||||
|
||||
:param group: The IfcGroup entity you want to edit
|
||||
:type group: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
group = ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
|
||||
ifcopenshell.api.run("group.edit_group", model,
|
||||
group=group, attributes={"Description": "All furniture and joinery included in the unit"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"group": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"group": group, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
|
||||
@@ -16,15 +16,48 @@
|
||||
# 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
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, group=None):
|
||||
"""Removes a group
|
||||
|
||||
All products assigned to the group will remain, but the relationship to
|
||||
the group will be removed.
|
||||
|
||||
:param group: The IfcGroup entity you want to remove
|
||||
:type group: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
group = ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
|
||||
ifcopenshell.api.run("group.remove_group", model, group=group)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"group": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings = {"group": group}
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["group"].IsGroupedBy or []:
|
||||
self.file.remove(rel)
|
||||
for inverse_id in [i.id() for i in self.file.get_inverse(self.settings["group"])]:
|
||||
try:
|
||||
inverse = self.file.by_id(inverse_id)
|
||||
except:
|
||||
continue
|
||||
if inverse.is_a("IfcRelDefinesByProperties"):
|
||||
ifcopenshell.api.run(
|
||||
"pset.remove_pset",
|
||||
self.file,
|
||||
product=self.settings["group"],
|
||||
pset=inverse.RelatingPropertyDefinition,
|
||||
)
|
||||
elif inverse.is_a("IfcRelAssignsToGroup"):
|
||||
if inverse.RelatingGroup == self.settings["group"]:
|
||||
self.file.remove(inverse)
|
||||
elif len(inverse.RelatedObjects) == 1:
|
||||
self.file.remove(inverse)
|
||||
self.file.remove(self.settings["group"])
|
||||
|
||||
@@ -21,14 +21,34 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, product=None, group=None):
|
||||
"""Unassigns a product from a group
|
||||
|
||||
If the product isn't assigned to the group, nothing will happen.
|
||||
|
||||
:param product: A IfcProduct element to unassign from the group
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param group: The IfcGroup to unassign from
|
||||
:type group: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
|
||||
furniture = model.by_type("IfcFurniture")
|
||||
ifcopenshell.api.run("group.assign_group", model, products=furniture, group=group)
|
||||
|
||||
bad_furniture = furniture[0]
|
||||
ifcopenshell.api.run("group.unassign_group", model, product=bad_furniture, group=group)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"product": None,
|
||||
"group": None,
|
||||
"product": product,
|
||||
"group": group,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["group"].IsGroupedBy:
|
||||
|
||||
@@ -21,14 +21,32 @@ import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
def __init__(self, file, group=None, products=None):
|
||||
"""Sets a group products to be an explicit list of products
|
||||
|
||||
Any previous products assigned to that group will have their assignment
|
||||
removed.
|
||||
|
||||
:param products: A list of IfcProduct elements to assign to the group
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param group: The IfcGroup to assign the products to
|
||||
:type group: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAssignsToGroup relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
|
||||
ifcopenshell.api.run("group.update_group_products", model,
|
||||
products=model.by_type("IfcFurniture"), group=group)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"group": None,
|
||||
"products": None,
|
||||
"group": group,
|
||||
"products": products,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["group"].IsGroupedBy:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user